<?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: codemee</title>
    <description>The latest articles on DEV Community by codemee (@codemee).</description>
    <link>https://dev.to/codemee</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%2F415015%2F26dc8621-f8da-4946-a902-17486b64662c.png</url>
      <title>DEV Community: codemee</title>
      <link>https://dev.to/codemee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codemee"/>
    <language>en</language>
    <item>
      <title>Codex 的 Subagents 數量上限</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Sun, 05 Jul 2026 11:55:29 +0000</pubDate>
      <link>https://dev.to/codemee/codex-de-subagents-shu-liang-shang-xian-2c67</link>
      <guid>https://dev.to/codemee/codex-de-subagents-shu-liang-shang-xian-2c67</guid>
      <description>&lt;p&gt;本文以此版文為測試依據：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;codex &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;codex-cli 0.142.5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在 Codex 的 Subagents 文件中提到，同時可以執行的 Subagents 數量由設定檔中的 &lt;code&gt;agents.max_threads&lt;/code&gt; 決定，沒有設定時，預設是 6，不過時測時預設卻不是 6：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"最多可以執行幾個 subagent？"&lt;/span&gt; 2&amp;gt;&lt;span class="nv"&gt;$null&lt;/span&gt; | glow &lt;span class="nt"&gt;-s&lt;/span&gt; light
&lt;span class="go"&gt;
  最多可同時執行 3 個 subagent；加上主 agent，共有 4 個並行執行槽位。
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你可以看到預設是 4 個。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;由於 &lt;code&gt;codex exec&lt;/code&gt; 會透過標準錯誤輸出額外的資訊，為了避免干擾我們檢視回覆內容，因此利用 &lt;code&gt;2&amp;gt;$nul&lt;/code&gt; 在 PowerShell 中把錯誤輸出轉向丟棄。另外，&lt;a href="https://github.com/charmbracelet/glow" rel="noopener noreferrer"&gt;&lt;code&gt;glow&lt;/code&gt;&lt;/a&gt; 是顯示 Markdown 文件的 CLI 工具。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但即使透過 &lt;code&gt;agents.max_threads&lt;/code&gt; 強制設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--config&lt;/span&gt; agents.max_threads&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="s2"&gt;"最多可以執行幾個 subagent？"&lt;/span&gt; 2&amp;gt;&lt;span class="nv"&gt;$null&lt;/span&gt; | glow &lt;span class="nt"&gt;-s&lt;/span&gt; light
&lt;span class="go"&gt;
  最多可同時執行 3 個 subagent；加上我這個主 agent，共 4 個並行名額。
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果依然沒變，真是奇怪。還好，Codex CLI 是開放原始碼的專案，這種測試結果跟文件不一致的情況，就請 Codex 自己查看看原始碼：：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;codex&lt;/span&gt; &lt;span class="err"&gt;exec&lt;/span&gt; &lt;span class="err"&gt;"查一下原始碼中跟&lt;/span&gt; &lt;span class="err"&gt;subagent&lt;/span&gt; &lt;span class="err"&gt;數量上限有關的部分有哪些？查到後告訴我設定的方式"&lt;/span&gt; &lt;span class="err"&gt;2&amp;gt;$null&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;glow&lt;/span&gt; &lt;span class="err"&gt;-s&lt;/span&gt; &lt;span class="err"&gt;light&lt;/span&gt;

  &lt;span class="err"&gt;目前&lt;/span&gt; &lt;span class="err"&gt;Codex&lt;/span&gt; &lt;span class="err"&gt;原始碼有兩套&lt;/span&gt; &lt;span class="err"&gt;subagent&lt;/span&gt; &lt;span class="err"&gt;數量限制，取決於使用哪一版&lt;/span&gt; &lt;span class="err"&gt;multi-agent。&lt;/span&gt;

  &lt;span class="c"&gt;### 1. 傳統 multi-agent                                          &lt;/span&gt;

  &lt;span class="err"&gt;設定鍵：&lt;/span&gt;                                                         

    &lt;span class="nn"&gt;[features]&lt;/span&gt;                                                     
    &lt;span class="py"&gt;multi_agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                                             

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                                       
    &lt;span class="py"&gt;max_threads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;                                               

  &lt;span class="err"&gt;預設值是&lt;/span&gt;  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="err"&gt;：&lt;/span&gt;                                                   

    &lt;span class="err"&gt;DEFAULT_AGENT_MAX_THREADS:&lt;/span&gt; &lt;span class="py"&gt;Option&amp;lt;usize&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;Some(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;);&lt;/span&gt;            

  &lt;span class="err"&gt;主要相關原始碼：&lt;/span&gt;                                                 

  &lt;span class="err"&gt;•&lt;/span&gt;  &lt;span class="err"&gt;codex-rs/core/src/config/mod.rs&lt;/span&gt;  &lt;span class="err"&gt;https://github.&lt;/span&gt;              
  &lt;span class="err"&gt;com/openai/codex/blob/main/codex-rs/core/src/config/mod.rs：預設值、讀取&lt;/span&gt;
  &lt;span class="err"&gt;agents.max_threads&lt;/span&gt;                                               
  &lt;span class="err"&gt;•&lt;/span&gt;  &lt;span class="err"&gt;codex-rs/core/src/agent/control.rs&lt;/span&gt;  &lt;span class="err"&gt;https://github.&lt;/span&gt;           
  &lt;span class="err"&gt;com/openai/codex/blob/main/codex-rs/core/src/agent/control.rs：把限制交給&lt;/span&gt;
  &lt;span class="err"&gt;session&lt;/span&gt; &lt;span class="err"&gt;執行&lt;/span&gt; &lt;span class="err"&gt;limiter&lt;/span&gt;                                             
  &lt;span class="err"&gt;•&lt;/span&gt;  &lt;span class="err"&gt;codex-rs/core/src/agent/registry.rs&lt;/span&gt;  &lt;span class="err"&gt;https://github.&lt;/span&gt;          
  &lt;span class="err"&gt;com/openai/codex/blob/main/codex-rs/core/src/agent/registry.rs：配置與回收&lt;/span&gt;
  &lt;span class="err"&gt;spawn&lt;/span&gt; &lt;span class="err"&gt;slot&lt;/span&gt;                                                       
  &lt;span class="err"&gt;•&lt;/span&gt;  &lt;span class="err"&gt;codex-rs/protocol/src/error.rs&lt;/span&gt;  &lt;span class="err"&gt;https://github.&lt;/span&gt;               
  &lt;span class="err"&gt;com/openai/codex/blob/main/codex-rs/protocol/src/error.rs：超過限制時的&lt;/span&gt;
  &lt;span class="err"&gt;AgentLimitReached&lt;/span&gt;                                                

  &lt;span class="err"&gt;也可以只對單次&lt;/span&gt; &lt;span class="err"&gt;CLI&lt;/span&gt; &lt;span class="err"&gt;執行覆寫：&lt;/span&gt;                                    

    &lt;span class="err"&gt;codex&lt;/span&gt; &lt;span class="err"&gt;-c&lt;/span&gt; &lt;span class="py"&gt;agents.max_threads&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;                                 

  &lt;span class="err"&gt;或：&lt;/span&gt;                                                             

    &lt;span class="err"&gt;codex&lt;/span&gt; &lt;span class="err"&gt;exec&lt;/span&gt; &lt;span class="err"&gt;-c&lt;/span&gt; &lt;span class="py"&gt;agents.max_threads&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="s"&gt;"你的任務"&lt;/span&gt;                 

  &lt;span class="c"&gt;### 2. Multi-agent V2                                            &lt;/span&gt;

  &lt;span class="err"&gt;新版另有獨立設定：&lt;/span&gt;                                               

    &lt;span class="nn"&gt;[features.multi_agent_v2]&lt;/span&gt;                                      
    &lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                                                 
    &lt;span class="py"&gt;max_concurrent_threads_per_session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;                         

  &lt;span class="err"&gt;預設值是&lt;/span&gt;  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="err"&gt;：&lt;/span&gt;                                                   

    &lt;span class="err"&gt;DEFAULT_MULTI_AGENT_V&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;_MAX_CONCURRENT_THREADS_PER_SESSION:&lt;/span&gt; &lt;span class="py"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;

  &lt;span class="err"&gt;Schema&lt;/span&gt; &lt;span class="err"&gt;規定最小值是&lt;/span&gt;  &lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="err"&gt;，原始碼目前看不到硬性最大值。設定&lt;/span&gt; &lt;span class="err"&gt;Schema&lt;/span&gt; 
  &lt;span class="err"&gt;https://github.com/openai/codex/blob/main/codex-rs/core/config.schema.json&lt;/span&gt;

  &lt;span class="err"&gt;你這個工作階段顯示「4&lt;/span&gt; &lt;span class="err"&gt;available&lt;/span&gt; &lt;span class="err"&gt;concurrency&lt;/span&gt; &lt;span class="err"&gt;slots」，與&lt;/span&gt; &lt;span class="err"&gt;V2&lt;/span&gt;       
  &lt;span class="err"&gt;的預設值完全一致，所以如果要改目前看到的&lt;/span&gt; &lt;span class="err"&gt;4，應優先設定：&lt;/span&gt;         

    &lt;span class="nn"&gt;[features.multi_agent_v2]&lt;/span&gt;                                      
    &lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                                                 
    &lt;span class="py"&gt;max_concurrent_threads_per_session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;                         

  &lt;span class="err"&gt;設定檔位置通常是&lt;/span&gt; &lt;span class="err"&gt;Windows&lt;/span&gt; &lt;span class="err"&gt;的：&lt;/span&gt;                                    

    &lt;span class="err"&gt;%USERPROFILE%\.codex\config.toml&lt;/span&gt;                               

  &lt;span class="err"&gt;修改後需重新啟動&lt;/span&gt; &lt;span class="err"&gt;Codex&lt;/span&gt; &lt;span class="err"&gt;session。&lt;/span&gt; &lt;span class="err"&gt;agents.max_threads&lt;/span&gt;  &lt;span class="err"&gt;和&lt;/span&gt; &lt;span class="err"&gt;V&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="err"&gt;的&lt;/span&gt;    
  &lt;span class="err"&gt;max_concurrent_threads_per_session&lt;/span&gt;                               
  &lt;span class="err"&gt;是不同限制；為避免版本或功能切換造成落差，也可以兩個都設：&lt;/span&gt;       

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;features&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                                     
    &lt;span class="py"&gt;multi_agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                                             

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;features.multi_agent_v&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                      
    &lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                                                 
    &lt;span class="py"&gt;max_concurrent_threads_per_session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;                         

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                                       
    &lt;span class="py"&gt;max_threads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;                                                

  &lt;span class="err"&gt;數值開太高可能增加&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="err"&gt;、token&lt;/span&gt; &lt;span class="err"&gt;消耗和資源競爭；官方把舊版預設設為&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;、新版設為&lt;/span&gt;
  &lt;span class="err"&gt;4，應是刻意的保守值。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你可以看到居然有兩套 Subagent 機制，該不會現在 Codex 其實是採用文件上沒有寫的 multi_agent_v2 吧？&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;codex&lt;/span&gt; &lt;span class="err"&gt;exec&lt;/span&gt; &lt;span class="err"&gt;"現在是不是有啟用&lt;/span&gt; &lt;span class="err"&gt;multi_agent_v2？有的話，要如何設定&lt;/span&gt; &lt;span class="err"&gt;Subagent&lt;/span&gt; &lt;span class="err"&gt;數量上限？"&lt;/span&gt; &lt;span class="err"&gt;2&amp;gt;$null&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;glow&lt;/span&gt; &lt;span class="err"&gt;-s&lt;/span&gt; &lt;span class="err"&gt;light&lt;/span&gt;

  &lt;span class="err"&gt;沒有啟用&lt;/span&gt;  &lt;span class="err"&gt;multi_agent_v2&lt;/span&gt; &lt;span class="err"&gt;。本機狀態是：&lt;/span&gt;                          

    &lt;span class="err"&gt;multi_agent&lt;/span&gt;       &lt;span class="err"&gt;stable&lt;/span&gt;             &lt;span class="kc"&gt;true&lt;/span&gt;                      
    &lt;span class="err"&gt;multi_agent_v2&lt;/span&gt;    &lt;span class="err"&gt;under&lt;/span&gt; &lt;span class="err"&gt;development&lt;/span&gt;  &lt;span class="kc"&gt;false&lt;/span&gt;                     

  &lt;span class="err"&gt;目前工作階段上限是&lt;/span&gt; &lt;span class="err"&gt;4&lt;/span&gt; &lt;span class="err"&gt;個&lt;/span&gt; &lt;span class="err"&gt;agent&lt;/span&gt; &lt;span class="err"&gt;thread（包含主&lt;/span&gt; &lt;span class="err"&gt;Agent），因此最多同時執行&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt; &lt;span class="err"&gt;個&lt;/span&gt;
  &lt;span class="err"&gt;Subagent。&lt;/span&gt;                                                       

  &lt;span class="err"&gt;若要設定全域上限，編輯&lt;/span&gt;  &lt;span class="err"&gt;%USERPROFILE%\.codex\config.toml&lt;/span&gt; &lt;span class="err"&gt;：&lt;/span&gt;      

    &lt;span class="nn"&gt;[agents]&lt;/span&gt;                                                       
    &lt;span class="py"&gt;max_threads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;                                                
    &lt;span class="py"&gt;max_depth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;                                                  

  &lt;span class="err"&gt;也可放在專案的&lt;/span&gt;  &lt;span class="err"&gt;.codex/config.toml&lt;/span&gt; &lt;span class="err"&gt;。&lt;/span&gt; &lt;span class="err"&gt;max_threads&lt;/span&gt;  &lt;span class="err"&gt;預設為&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;；&lt;/span&gt; &lt;span class="err"&gt;max_depth&lt;/span&gt;
  &lt;span class="err"&gt;控制&lt;/span&gt; &lt;span class="err"&gt;Subagent&lt;/span&gt; &lt;span class="err"&gt;可繼續產生幾層子代理。設定後重開&lt;/span&gt; &lt;span class="err"&gt;Codex&lt;/span&gt; &lt;span class="err"&gt;工作階段。&lt;/span&gt;  

  &lt;span class="err"&gt;若要實驗性啟用&lt;/span&gt; &lt;span class="err"&gt;v2：&lt;/span&gt;                                              

    &lt;span class="err"&gt;codex&lt;/span&gt; &lt;span class="err"&gt;features&lt;/span&gt; &lt;span class="err"&gt;enable&lt;/span&gt; &lt;span class="err"&gt;multi_agent_v2&lt;/span&gt;                           

  &lt;span class="err"&gt;但一般&lt;/span&gt; &lt;span class="err"&gt;Subagent&lt;/span&gt; &lt;span class="err"&gt;工作目前使用穩定的&lt;/span&gt;  &lt;span class="err"&gt;multi_agent&lt;/span&gt;  &lt;span class="err"&gt;即可，無須啟用&lt;/span&gt;  
  &lt;span class="err"&gt;v2。參考：Subagents&lt;/span&gt;                                              
  &lt;span class="err"&gt;https://developers.openai.com/codex/subagents、Configuration&lt;/span&gt; &lt;span class="err"&gt;reference&lt;/span&gt;
  &lt;span class="err"&gt;https://developers.openai.com/codex/config-reference。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;咦？並沒有啟用 multi_agent_v2，但 multi_agent_v2 預設限制是 4 個 Subagent，這看起來跟我們前面的測試相符，死馬當活馬醫，如果試試用用 multi_agent_v2 的方式設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--config&lt;/span&gt; features.multi_agent_v2.max_concurrent_threads_per_session&lt;span class="o"&gt;=&lt;/span&gt;6 &lt;span class="s2"&gt;"最多可以執行幾個 subagent？"&lt;/span&gt; 2&amp;gt;&lt;span class="nv"&gt;$null&lt;/span&gt; | glow &lt;span class="nt"&gt;-s&lt;/span&gt; light
&lt;span class="go"&gt;
  最多可同時執行 5 個 subagent，加上主 agent 共 6 個並行工作槽位。
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;成功了，雖然沒有啟用  multi_agent_v2，但卻要照著 multi_agent_v2 的方式設定，原因不知道，但至少目前可行。&lt;/p&gt;

</description>
      <category>codex</category>
      <category>agents</category>
    </item>
    <item>
      <title>誠品線上庫存查詢工具 — 技術報告</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Sun, 05 Jul 2026 06:02:10 +0000</pubDate>
      <link>https://dev.to/codemee/cheng-pin-xian-shang-ku-cun-cha-xun-gong-ju-ji-shu-bao-gao-3mnk</link>
      <guid>https://dev.to/codemee/cheng-pin-xian-shang-ku-cun-cha-xun-gong-ju-ji-shu-bao-gao-3mnk</guid>
      <description>&lt;p&gt;⚠ 本文由 Claude Code 生成&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 問題背景
&lt;/h2&gt;

&lt;p&gt;誠品線上（eslite.com）的商品頁面雖然會顯示庫存狀態，但僅能逐一開啟網頁人工確認，無法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一次查詢多筆商品&lt;/li&gt;
&lt;li&gt;對限量或熱銷商品做「上架/補貨」的持續監控&lt;/li&gt;
&lt;li&gt;以結構化格式（JSON）取得資料供其他程式串接&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此本專案目標是撰寫一支輕量 CLI 工具，直接呼叫誠品線上背後的商品 API，取代人工刷新網頁的行為。開發過程中遇到的核心技術問題並非「業務邏輯」，而是&lt;strong&gt;如何繞過前端防護、正確組出可用的 API 請求&lt;/strong&gt;。以下逐項說明。&lt;/p&gt;




&lt;h2&gt;
  
  
  2. 核心技術問題與解法
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Cloudflare TLS 指紋偵測導致 403
&lt;/h3&gt;

&lt;p&gt;誠品線上的 API 後端（&lt;code&gt;athena.eslite.com&lt;/code&gt;）架設在 Cloudflare 之後。Cloudflare 除了檢查 HTTP Header，還會在 TLS handshake 階段比對 &lt;strong&gt;JA3/JA3S 指紋&lt;/strong&gt;（ClientHello 中的 cipher suite 順序、TLS 擴充欄位順序、支援的橢圓曲線等）。&lt;/p&gt;

&lt;p&gt;Python 的 &lt;code&gt;requests&lt;/code&gt; 或 &lt;code&gt;httpx&lt;/code&gt; 底層使用 OpenSSL 產生的 TLS 指紋，與真實 Chrome 瀏覽器不同，即使把 &lt;code&gt;User-Agent&lt;/code&gt; 偽裝成 Chrome，Cloudflare 仍能從 TLS 層辨識出「非瀏覽器流量」並直接回傳 &lt;code&gt;403 Forbidden&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="c1"&gt;# ❌ 會被 Cloudflare 擋下（TLS 指紋不符，即使偽造了 User-Agent）
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://athena.eslite.com/api/v1/products/10012013492683166052007&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;User-Agent&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;Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 403
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;解法&lt;/strong&gt;：改用 &lt;a href="https://github.com/yifeikong/curl-cffi" rel="noopener noreferrer"&gt;&lt;code&gt;curl-cffi&lt;/code&gt;&lt;/a&gt;，它底層綁定 curl-impersonate，能重現特定瀏覽器版本（如 Chrome 120）的實際 TLS/HTTP2 指紋，而非只偽造 Header：&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;# ✅ curl-cffi 以 impersonate 參數重現真實 Chrome 的 TLS 指紋
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;curl_cffi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cffi_requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cffi_requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://athena.eslite.com/api/v1/products/10012013492683166052007&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;Accept&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;application/json&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;Referer&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;https://www.eslite.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;impersonate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chrome120&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;impersonate="chrome120"&lt;/code&gt; 這行是整個工具能否運作的關鍵，缺少它請求一律被 Cloudflare 攔截。&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 API 要求動態時間戳參數
&lt;/h3&gt;

&lt;p&gt;觀察誠品線上網頁實際發出的請求後發現，商品 API 每次呼叫都必須帶上 &lt;code&gt;datetime&lt;/code&gt; query 參數，格式為 &lt;code&gt;YYYYMMDDHHmmss&lt;/code&gt;（如 &lt;code&gt;20260705143210&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;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;dict&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://athena.eslite.com/api/v1/products/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y%m%d%H%M%S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cffi_requests&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&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;datetime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;impersonate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chrome120&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;若省略此參數或帶入固定值，部分情況下 API 會回傳錯誤或非預期的快取結果，因此每次請求都即時產生當下時間戳。&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 商品 GUID 的容錯擷取
&lt;/h3&gt;

&lt;p&gt;使用者輸入的來源可能是純數字 GUID，也可能是完整商品頁 URL（如 &lt;code&gt;https://www.eslite.com/product/10012013492683166052007&lt;/code&gt;）。為避免要求使用者自行複製 GUID，改用正規表達式從任意字串中擷取 20 位以上的連續數字：&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;re&lt;/span&gt;

&lt;span class="n"&gt;_GUID_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d{20,}&lt;/span&gt;&lt;span class="sh"&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;extract_guid&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="nb"&gt;str&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_GUID_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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;ValueError&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;無法從 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; 解析出商品 GUID（需為 20 位以上數字）&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這讓 CLI 同時支援兩種輸入形式：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run eslite_stock.py 10012013492683166052007
uv run eslite_stock.py https://www.eslite.com/product/10012013492683166052007
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.4 持續監控（--watch）與狀態顯示
&lt;/h3&gt;

&lt;p&gt;限量書籍的補貨時機難以預測，因此加入 &lt;code&gt;--watch N&lt;/code&gt; 參數，每 N 秒重新查詢並清空終端機重繪，直到使用者按 &lt;code&gt;Ctrl+C&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="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;watch&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;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;query_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guids&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;raw_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;console&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;[dim]每 &lt;/span&gt;&lt;span class="si"&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;watch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 秒更新一次，按 Ctrl+C 結束[/dim]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;watch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;console&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[dim]已停止監控[/dim]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&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="n"&gt;_BUTTON_LABEL&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;add_to_shopping_cart&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;[bold green]可加入購物車[/bold green]&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;out_of_stock&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;[bold red]缺貨[/bold red]&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;not_for_sale&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;[yellow]停售[/yellow]&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;pre_order&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;[cyan]預購中[/cyan]&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.5 多目標查詢與錯誤隔離
&lt;/h3&gt;

&lt;p&gt;支援一次傳入多個 GUID/URL，且單一商品查詢失敗（如 GUID 打錯、該商品已下架）不應中斷整批查詢：&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;raw_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guids&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;console&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;[red]HTTP &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;（GUID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;）[/red]&lt;/span&gt;&lt;span class="sh"&gt;"&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;console&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;[red]錯誤：&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;（GUID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;）[/red]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;每筆商品各自 try/except，確保部分失敗不影響其餘商品的查詢結果。&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6 Windows 終端機中文編碼相容性
&lt;/h3&gt;

&lt;p&gt;在部分 Windows 終端環境（如舊版 &lt;code&gt;cmd.exe&lt;/code&gt;）下，&lt;code&gt;sys.stdout&lt;/code&gt; 預設編碼並非 UTF-8，直接輸出中文書名或 rich 的方塊字元會出現亂碼或 &lt;code&gt;UnicodeEncodeError&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="k"&gt;if&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;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="ow"&gt;and&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;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconfigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="nf"&gt;reconfigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.7 PEP 723 獨立腳本（零安裝執行）
&lt;/h3&gt;

&lt;p&gt;本工具以 &lt;a href="https://peps.python.org/pep-0723/" rel="noopener noreferrer"&gt;PEP 723&lt;/a&gt; 內嵌相依套件宣告，使用者無需事先 &lt;code&gt;pip install&lt;/code&gt; 或維護虛擬環境，&lt;code&gt;uv run&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="c1"&gt;# /// script
# requires-python = "&amp;gt;=3.11"
# dependencies = [
#   "curl-cffi&amp;gt;=0.7",
#   "rich&amp;gt;=13",
# ]
# ///
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run eslite_stock.py &amp;lt;GUID或URL&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--watch&lt;/span&gt; 秒] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--json&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. 架構小結
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;使用者輸入 (GUID / URL)
        │
        ▼
  extract_guid()  ──▶ 正規表達式擷取 20 位以上數字
        │
        ▼
  fetch_product()  ──▶ curl-cffi + impersonate="chrome120" 繞過 Cloudflare TLS 指紋偵測
        │              帶入即時 datetime 參數
        ▼
  athena.eslite.com/api/v1/products/{guid}
        │
        ▼
  show_product() / json.dumps()  ──▶ rich 彩色輸出 或 原始 JSON
        │
        ▼
  （若 --watch）迴圈重新查詢，Ctrl+C 結束
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;整個專案最主要、也最不易從程式碼本身看出「為什麼這樣寫」的技術決策，是 &lt;strong&gt;2.1 節的 TLS 指紋問題&lt;/strong&gt;：一般開發者在遇到 API 回傳 403 時，直覺會先懷疑 Header 或 Cookie，但根因其實在 TLS 層，必須改用能重現瀏覽器指紋的函式庫（curl-cffi）才能解決，單純偽造 &lt;code&gt;User-Agent&lt;/code&gt; 完全無效。&lt;/p&gt;

</description>
      <category>爬蟲</category>
      <category>curlcffi</category>
      <category>python</category>
      <category>crawler</category>
    </item>
    <item>
      <title>Tkinter 快顯功能表關閉殘影調查報告</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Sun, 05 Jul 2026 05:57:10 +0000</pubDate>
      <link>https://dev.to/codemee/tkinter-kuai-xian-gong-neng-biao-guan-bi-can-ying-diao-cha-bao-gao-12f7</link>
      <guid>https://dev.to/codemee/tkinter-kuai-xian-gong-neng-biao-guan-bi-can-ying-diao-cha-bao-gao-12f7</guid>
      <description>&lt;p&gt;⚠ 本篇報告全文由 Codex 生成&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 摘要
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;test_tk_manual.py&lt;/code&gt; 在 Windows 上顯示 Tkinter 右鍵快顯功能表後，若使用者選擇 &lt;code&gt;Close&lt;/code&gt; 並直接在選單 callback 中執行 &lt;code&gt;root.destroy()&lt;/code&gt;，主視窗雖然關閉，畫面上卻可能暫時留下選單殘影。&lt;/p&gt;

&lt;p&gt;調查確認，問題核心不是一般 Tk widget 沒有刷新，而是 Windows 上的 Tk 選單使用原生 menu loop。選單 callback 執行時，&lt;code&gt;menu.tk_popup()&lt;/code&gt; 可能仍未返回；此時銷毀選單的 owner 視窗，會破壞 Windows 正常結束與重繪選單的時序。&lt;/p&gt;

&lt;p&gt;目前的修正策略如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;選單 callback 不直接銷毀主視窗。&lt;/li&gt;
&lt;li&gt;callback 設定關閉旗標。&lt;/li&gt;
&lt;li&gt;呼叫 Windows &lt;code&gt;EndMenu()&lt;/code&gt;，明確結束目前執行緒的原生 menu loop。&lt;/li&gt;
&lt;li&gt;等 &lt;code&gt;menu.tk_popup()&lt;/code&gt; 返回後，再由外層事件處理函式執行 &lt;code&gt;root.destroy()&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;目前版本已通過 Python 語法檢查；最終視覺效果仍需由使用者在實際桌面環境確認。&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 原始展示範例
&lt;/h2&gt;

&lt;p&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;tkinter&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;300x100+100+100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;menu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tearoff&lt;/span&gt;&lt;span class="o"&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;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destroy&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;show_menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tk_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;Button-3&amp;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;show_menu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mainloop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;重現步驟：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;執行程式。&lt;/li&gt;
&lt;li&gt;在主視窗按滑鼠右鍵。&lt;/li&gt;
&lt;li&gt;在快顯功能表選擇 &lt;code&gt;Close&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;觀察主視窗消失後，選單區域是否留下殘影。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&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="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;它會在選單 callback 尚處於原生選單處理流程時，立即銷毀 owner 視窗。&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 平台行為與根因
&lt;/h2&gt;

&lt;p&gt;Tk 的選單行為會依視窗平台而不同。Tk 開發資料指出，在 Windows 上選單由原生機制處理，而且 menu post 呼叫可能在選單關閉前不返回，但等待期間仍會處理事件。參考資料：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://core.tcl-lang.org/tk/tktview/c77b42" rel="noopener noreferrer"&gt;Tk ticket：Windows 上的 menu post 行為&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.tcl-lang.org/tk/artifact/6b33a1452c19b104" rel="noopener noreferrer"&gt;Tk &lt;code&gt;menu.tcl&lt;/code&gt; 原始碼與 &lt;code&gt;MenuUnpost&lt;/code&gt; 流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-endmenu" rel="noopener noreferrer"&gt;Microsoft &lt;code&gt;EndMenu&lt;/code&gt; API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;事件順序可概括為：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;使用者點選 Close
        |
        v
Tk 執行選單 command callback
        |
        |  此時 Windows menu loop 可能仍在運作
        v
若 callback 直接 root.destroy()
        |
        v
owner 視窗提前消失，選單清除／重繪時序不完整
        |
        v
可能留下快顯功能表殘影
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;因此，安全的關閉點不是選單 callback 本身，而是原生 menu loop 結束、&lt;code&gt;tk_popup()&lt;/code&gt; 返回之後。&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 修正歷程
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 直接銷毀主視窗
&lt;/h3&gt;

&lt;p&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="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果：主視窗關閉，但 Windows 桌面可能留下快顯功能表殘影。&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 &lt;code&gt;unpost()&lt;/code&gt;、釋放 grab，再用 &lt;code&gt;after_idle()&lt;/code&gt; 關閉
&lt;/h3&gt;

&lt;p&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close_window&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grab_release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_idle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果：殘影仍存在。&lt;/p&gt;

&lt;p&gt;原因：&lt;code&gt;after_idle()&lt;/code&gt; 只保證目前 Tk callback 返回後執行，不保證 Windows 原生 menu loop 已經退出。&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 延遲 100 ms 後關閉
&lt;/h3&gt;

&lt;p&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="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果：殘影仍存在。&lt;/p&gt;

&lt;p&gt;原因：原生選單等待期間仍會處理 Tk 事件，因此計時器可能在 &lt;code&gt;tk_popup()&lt;/code&gt; 尚未返回時觸發。增加任意延遲不能建立可靠的事件順序，只會造成時間相依的行為。&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 callback 只設定旗標
&lt;/h3&gt;

&lt;p&gt;第三次嘗試讓 callback 只記錄關閉要求：&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close_window&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;close_requested&lt;/span&gt;
    &lt;span class="n"&gt;close_requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;並在 &lt;code&gt;tk_popup()&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="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tk_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_root&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;close_requested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果：選擇 &lt;code&gt;Close&lt;/code&gt; 後主視窗沒有立即關閉，必須再按一下滑鼠，選單與主視窗才一起消失。&lt;/p&gt;

&lt;p&gt;原因：只設定旗標沒有主動終止目前的原生 menu loop，所以 &lt;code&gt;tk_popup()&lt;/code&gt; 仍未返回，外層的 &lt;code&gt;root.destroy()&lt;/code&gt; 尚無機會執行。&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 使用 &lt;code&gt;menu.unpost()&lt;/code&gt; 或 &lt;code&gt;tk::MenuUnpost&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;後續分別測試 widget 層級的：&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;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以及 Tk 內部完整狀態清理：&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;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tk::MenuUnpost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果：實際環境仍需額外滑鼠點擊才能讓 &lt;code&gt;tk_popup()&lt;/code&gt; 返回。&lt;/p&gt;

&lt;p&gt;這表示 Tk 層級的 unpost 清理在此 Windows 原生 menu loop 中不足以立即終止追蹤流程。&lt;/p&gt;

&lt;h3&gt;
  
  
  4.6 使用 Windows &lt;code&gt;EndMenu()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;目前版本改用 Windows API：&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;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user32&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EndMenu&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EndMenu()&lt;/code&gt; 的用途是結束呼叫執行緒目前作用中的選單。callback 僅終止 menu loop，不銷毀 owner；等 &lt;code&gt;tk_popup()&lt;/code&gt; 返回後，才關閉主視窗。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 目前完整展示範例
&lt;/h2&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;ctypes&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tkinter&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;300x100+100+100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Right-click me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;both&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expand&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="n"&gt;menu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tearoff&lt;/span&gt;&lt;span class="o"&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;close_requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close_window&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;close_requested&lt;/span&gt;
    &lt;span class="n"&gt;close_requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user32&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EndMenu&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;close_window&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;show_menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tk_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_root&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;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grab_release&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;close_requested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;Button-3&amp;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;show_menu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;Button-3&amp;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;show_menu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mainloop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. 修正版事件順序
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;使用者點選 Close
        |
        v
close_window() 設定 close_requested
        |
        v
EndMenu() 結束 Windows 原生 menu loop
        |
        v
menu.tk_popup() 返回
        |
        v
finally 釋放 menu grab
        |
        v
show_menu() 檢查 close_requested
        |
        v
root.destroy() 安全銷毀主視窗
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. 驗證方式
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 語法檢查
&lt;/h3&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\python.exe&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;py_compile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test_tk_manual.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;預期結果：命令結束碼為 &lt;code&gt;0&lt;/code&gt;，沒有輸出語法錯誤。&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 手動視覺測試
&lt;/h3&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\pythonw.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\test_tk_manual.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;測試項目：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;編號&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;預期結果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;在主視窗按右鍵&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Close&lt;/code&gt; 選單立即出現&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;點選 &lt;code&gt;Close&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;選單與主視窗立即消失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;觀察原選單位置&lt;/td&gt;
&lt;td&gt;不留下選單內容或陰影殘影&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;再次執行，點選選單外部&lt;/td&gt;
&lt;td&gt;選單取消，主視窗保持開啟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;重複快速開啟與取消選單&lt;/td&gt;
&lt;td&gt;不應卡住 grab 或要求額外點擊&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  7.3 驗收狀態
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;狀態&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python 語法檢查&lt;/td&gt;
&lt;td&gt;已通過&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原始問題重現&lt;/td&gt;
&lt;td&gt;已確認&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;after_idle()&lt;/code&gt; 修正&lt;/td&gt;
&lt;td&gt;失敗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 ms 延遲修正&lt;/td&gt;
&lt;td&gt;失敗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tk &lt;code&gt;unpost&lt;/code&gt; 修正&lt;/td&gt;
&lt;td&gt;失敗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows &lt;code&gt;EndMenu()&lt;/code&gt; 修正&lt;/td&gt;
&lt;td&gt;已實作，待使用者確認視覺結果&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  8. 限制與後續考量
&lt;/h2&gt;

&lt;p&gt;目前修正呼叫 Windows API，因此是 Windows 專用方案。若程式未來需要跨平台，應依 &lt;code&gt;root.tk.call("tk", "windowingsystem")&lt;/code&gt; 分流：Windows 使用 &lt;code&gt;EndMenu()&lt;/code&gt;；其他平台保留 Tk 原生關閉流程。&lt;/p&gt;

&lt;p&gt;若 &lt;code&gt;EndMenu()&lt;/code&gt; 在目標機器上仍無法消除殘影，下一步不應再增加延遲時間，而應考慮：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;收集 Python、Tcl/Tk 與 Windows 的確切版本。&lt;/li&gt;
&lt;li&gt;建立獨立的最小重現程式並記錄螢幕畫面。&lt;/li&gt;
&lt;li&gt;改用自訂 &lt;code&gt;Toplevel&lt;/code&gt; 實作非原生快顯選單，以完全避開 Windows native menu loop。&lt;/li&gt;
&lt;li&gt;比較不同 Tcl/Tk 發行版本，確認是否為特定版本的 Windows 選單缺陷。&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>tkinter</category>
      <category>python</category>
      <category>windows</category>
      <category>tkpoppup</category>
    </item>
    <item>
      <title>Gemini 模型的 tokenizer</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Tue, 27 Jan 2026 02:52:19 +0000</pubDate>
      <link>https://dev.to/codemee/gemini-mo-xing-de-tokenizer-4bo0</link>
      <guid>https://dev.to/codemee/gemini-mo-xing-de-tokenizer-4bo0</guid>
      <description>&lt;p&gt;Gemini 並沒有提供類似 OpenAI 的 Tokenizer 頁面，方便大家觀察切割 token 的結果，如果你有使用 Vertex AI，則可以透過本文介紹的兩種方式查看。&lt;/p&gt;

&lt;h2&gt;
  
  
  Vertex AI Studio
&lt;/h2&gt;

&lt;p&gt;在 Vertex AI Studio 中，在輸入框輸入內容時，輸入框下緣會顯示目前的 token 數（Gemini 稱為『詞元』），像是底下的例子就告訴我們『這是什麼奇怪的世界？』會被切割為 5 個詞元：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy48ql8pwvmho0cngl97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy48ql8pwvmho0cngl97.png" alt=" " width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果想知道實際切割的結果，只要按一下顯示詞元數量的地方，就會在右側顯示詳細資料：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5bj0pa5subp9uvo6bct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5bj0pa5subp9uvo6bct.png" alt=" " width="700" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;它會以不同的背景色來標示每一個詞元的涵蓋範圍，你也可以切換頁次，觀察每個詞元對應的編號：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48q4sr2lu1papwuor8yi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48q4sr2lu1papwuor8yi.png" alt=" " width="700" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;要特別提醒你的是，Vertex AI Studio 和 Google AI Studio 雖然很相似，但這個觀察 token 切割結果的功能就只有 Vertex AI Studio 才有。&lt;/p&gt;

&lt;h2&gt;
  
  
  透過 Vertex AI API 取得切割結果
&lt;/h2&gt;

&lt;p&gt;如果你需要在程式中即時取得切割詞元的結果，也可以透過 &lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/list-token" rel="noopener noreferrer"&gt;Vertex AI API&lt;/a&gt;，在 GenAI SDK 的 &lt;code&gt;Client&lt;/code&gt; 類別中，提供有 &lt;a href="https://googleapis.github.io/python-genai/genai.html#genai.models.AsyncModels.compute_tokens" rel="noopener noreferrer"&gt;&lt;code&gt;models.compute_tokens&lt;/code&gt;&lt;/a&gt; 方法可以取得切割資訊，它的參數和 &lt;code&gt;generate_content&lt;/code&gt; 相同，但會傳回 &lt;a href="https://googleapis.github.io/python-genai/genai.html#genai.types.ComputeTokensResponse" rel="noopener noreferrer"&gt;&lt;code&gt;ComputeTokensResponse&lt;/code&gt;&lt;/a&gt; 類物件，它的 tokens_info 屬性是一個 &lt;a href="https://googleapis.github.io/python-genai/genai.html#genai.types.TokensInfo" rel="noopener noreferrer"&gt;&lt;code&gt;TokensInfo&lt;/code&gt;&lt;/a&gt; 類物件的串列，個別代表輸入的 &lt;code&gt;contents&lt;/code&gt; 中每個 &lt;code&gt;content&lt;/code&gt; 的切割結果，其中 &lt;code&gt;token_ids&lt;/code&gt; 是詞元編號的串列、&lt;code&gt;tokens&lt;/code&gt; 是個別詞元對應的文字段落，&lt;code&gt;role&lt;/code&gt; 是發言角色。&lt;/p&gt;

&lt;p&gt;以下我們就依據上述結構，設計了一個&lt;a href="https://github.com/codemee/gemini_tokenizer" rel="noopener noreferrer"&gt;簡單的 CLI 程式&lt;/a&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;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rich.console&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Console&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;import&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;os&lt;/span&gt;

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

&lt;span class="n"&gt;client_vertex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vertexai&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="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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VERTEX_API_KEY&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;color_map&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;3&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;#ccbfee&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;2&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;#beedc6&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;1&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;#f4d7ab&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;0&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;#f4aeb1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# '4': '#a4dcf3',
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Console&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="k"&gt;if&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;console&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;用法：main.py prompt [model_name]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bold red&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;prompt&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;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-3-pro-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client_vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute_tokens&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;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&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;共 &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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokens_info&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;token_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 個 token：&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokens_info&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;tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;color_index&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&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;color_map&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;bg_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color_map&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;color_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#ffffff&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;console&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;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;style&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;on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bg_color&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;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="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;console&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; &lt;/span&gt;&lt;span class="sh"&gt;"&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="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="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;你可以像是這樣使用，如果沒有指定模型，預設會是 gemini-3-pro-preview：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ uv run main.py "今天是偉大的日子" gemini-2.5-flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;它會比照 Vertex AI Studio 的顏色顯示底色：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwulfr0eukejtz0d57kd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwulfr0eukejtz0d57kd5.png" alt=" " width="205" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我也在 HuggingFace 上部署了一個 &lt;a href="https://huggingface.co/spaces/flagtech/gemini_tokenizer" rel="noopener noreferrer"&gt;Gradio 的版本&lt;/a&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdtp56d9h5gvef6u95ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdtp56d9h5gvef6u95ob.png" alt=" " width="791" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;再次提醒，&lt;code&gt;models.compute_tokens&lt;/code&gt; 只有 Vertex AI API 才能使用，如果你使用 Gemini API，雖然看起來也可以叫用，但是只會傳回錯誤，告訴你這個方法只有 Vertex AI API 才支援。我不知道 Google 到底在想什麼，切割 token 的資訊都要 Vertex AI 才提供，政策沒有變更之前，你就只能透過上述方法取得相關資訊，還好，&lt;code&gt;models.compute_tokens&lt;/code&gt; 並不會號用詞元，所以不會產生費用。&lt;/p&gt;

&lt;p&gt;在我的觀察中，Gemini 的 tokenizer 不會把單一中文字元切開成多個詞元，非句首的英文單字也不大會切割成多個詞元，所以如果單純以詞元數來看，相同 context windows 下 Gemini 模型可以吃進入比 OpenAI 模型更多的文本，這也是大部分文章沒有提到的一點。&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>tokenizer</category>
    </item>
    <item>
      <title>Colab 中在背景執行程式的方法</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Sat, 24 Jan 2026 03:59:37 +0000</pubDate>
      <link>https://dev.to/codemee/colab-zhong-zai-bei-jing-zhi-xing-cheng-shi-de-fang-fa-3kad</link>
      <guid>https://dev.to/codemee/colab-zhong-zai-bei-jing-zhi-xing-cheng-shi-de-fang-fa-3kad</guid>
      <description>&lt;p&gt;Colab 背後仰賴一個跑 Linux 的虛擬機器執行你的 Python 程式，如果要測試一些後端程式，其實也是沒有問題，尤其即使是免費用戶，也可以打開終端機直接操作背後的作業系統，非常方便。本文介紹在測試後端程式時常會需要在背景執行程式的方法。&lt;/p&gt;

&lt;h2&gt;
  
  
  直接開終端機下指令
&lt;/h2&gt;

&lt;p&gt;要在背景執行指令，最簡單的方法就是開啟終端機下指令，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/content# &lt;span class="nb"&gt;sleep &lt;/span&gt;10 &amp;amp;
&lt;span class="o"&gt;[&lt;/span&gt;1] 7542
/content# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;指令看起來會立即結束，如果以 &lt;code&gt;ps&lt;/code&gt; 指令檢查，就會看到在背景執行的指令：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/content# ps &lt;span class="nt"&gt;-eo&lt;/span&gt; pid,command | &lt;span class="nb"&gt;grep sleep
   &lt;/span&gt;7542 &lt;span class="nb"&gt;sleep &lt;/span&gt;10
   7554 &lt;span class="nb"&gt;sleep &lt;/span&gt;1
   7556 &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="nb"&gt;sleep&lt;/span&gt;
/content#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;等它結束後再度查詢：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/content# ps &lt;span class="nt"&gt;-eo&lt;/span&gt; pid,command | &lt;span class="nb"&gt;grep sleep
   &lt;/span&gt;7644 &lt;span class="nb"&gt;sleep &lt;/span&gt;1
   7646 &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="nb"&gt;sleep&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;1]+  Done                    &lt;span class="nb"&gt;sleep &lt;/span&gt;10
/content#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;就會看到原本在背景執行的 &lt;code&gt;sleep 10&lt;/code&gt; 已經不見了，而在查詢指令結束後，也會出現已經結束的背景程式資訊。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 &lt;code&gt;!&lt;/code&gt; 執行指令
&lt;/h2&gt;

&lt;p&gt;Colab 本質是 Jupyter，提供有 &lt;code&gt;!&lt;/code&gt; 特殊命令可以執行系統指令，所以理論上可以 &lt;code&gt;!sleep 10 &amp;amp;&lt;/code&gt; 這樣執行背景程式：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqog0qokmoccppa0nw7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqog0qokmoccppa0nw7q.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;但你會發現儲存格會一直處在執行中狀態，直到背景程式結束：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc003sgrjw7qry357iars.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc003sgrjw7qry357iars.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;你會看到儲存格的執行時間是 10 秒，這使得背景程式執行時無法執行其他儲存格。會發生這樣的狀況，是因為 &lt;code&gt;!&lt;/code&gt; 必須捕捉所執行程式的輸出，以便能夠顯示在儲存格的輸出區域，所以即使是以背景方式執行程式，但仍持續捕捉它的輸出，所以儲存格就會停留在執行中的狀態。&lt;/p&gt;

&lt;p&gt;要解決這個問題，最直接的方式就是把背景程式的輸出轉向，例如 &lt;code&gt;!sleep 10 &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysc6r03cuockcd03n2pi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysc6r03cuockcd03n2pi.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;你可以看到這裡儲存格的執行時間是 0 秒，也就是執行儲存格後就會離開執行中的狀態，即使背景程式仍在執行中，這樣就可以在背景執行必要的程式，但又可以執行其他儲存格了。要特別注意的是，轉向時，要把&lt;strong&gt;標準輸出&lt;/strong&gt;以及&lt;strong&gt;標準錯誤&lt;/strong&gt;都一併轉向。&lt;/p&gt;

&lt;p&gt;上述指令是把輸出都轉向到 Linux 的黑洞檔案 /dev/null 中，所以完全無法看到背景程式的輸出或錯誤訊息。如果需要看到輸出結果，也可以轉向到任一檔案，例如 &lt;code&gt;! python -c "print 'hello'" &amp;gt; out.log 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3x88xemfbukpflyc2n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3x88xemfbukpflyc2n1.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;就可以看到背景程式有噴出錯誤訊息，對於需要檢視後端程式輸出或除錯很有幫助。&lt;/p&gt;

&lt;h3&gt;
  
  
  使用 &lt;code&gt;nohup&lt;/code&gt; 協助轉向
&lt;/h3&gt;

&lt;p&gt;如果覺得上述輸出轉向語法有點複雜，也可以透過 &lt;code&gt;nohup&lt;/code&gt; 指令自動處理，這個指令原本是為了讓你可以在執行即使斷開當前終端機連線也會繼續執行的背景程式。前述的背景程式執行方法，會在你離開當前終端機階段（關閉終端機或是結束 ssh 連線等，稱為 &lt;strong&gt;hang up&lt;/strong&gt;，也是掛斷電話的意思）時一併結束，&lt;code&gt;nohangup&lt;/code&gt; 代表 "no hang up"，表示不理會掛斷。&lt;code&gt;nohup&lt;/code&gt; 會幫你把輸出轉向到 &lt;code&gt;nohup.out&lt;/code&gt; 檔，例如 &lt;code&gt;!nohup sleep 10 &amp;amp;&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa28n2vour9w90pe5q71k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa28n2vour9w90pe5q71k.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;執行後唯一的輸出就是告知輸出轉向檔案的訊息。&lt;/p&gt;

&lt;p&gt;要注意的是，&lt;code&gt;!&lt;/code&gt; 一樣會把儲存格輸出區的輸入轉向給執行的指令，如果沒有加上 &lt;code&gt;&amp;amp;&lt;/code&gt; 指定在背景執行，斷開儲存格輸入與指令的連結，一樣會處於等待輸入的狀態，像是 &lt;code&gt;!nohup sleep 10&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mu3j5pc3ckbahfihly5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mu3j5pc3ckbahfihly5.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;就無法執行其他儲存格了。&lt;/p&gt;

</description>
      <category>colab</category>
      <category>linux</category>
    </item>
    <item>
      <title>遠端使用 ollama 的方法</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Mon, 19 Jan 2026 06:52:04 +0000</pubDate>
      <link>https://dev.to/codemee/yuan-duan-shi-yong-ollama-de-fang-fa-2j5g</link>
      <guid>https://dev.to/codemee/yuan-duan-shi-yong-ollama-de-fang-fa-2j5g</guid>
      <description>&lt;p&gt;ollama 除了可以在本機使用外，也可以從遠端連接，不過預設 ollama 只接受本機的連線，無法從遠端連接。&lt;/p&gt;

&lt;h2&gt;
  
  
  設定可接受遠端連線
&lt;/h2&gt;

&lt;p&gt;要讓 ollama 接受遠端連線，必須在啟動 ollama 服務前設定 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 環境變數為 &lt;code&gt;0.0.0.0&lt;/code&gt;，否則就只會接收本機連線。設定環境變數時可設定為 &lt;code&gt;0.0.0.0&lt;/code&gt; 保留預設連接埠 &lt;code&gt;11434&lt;/code&gt;，或是也可以同時指定連接埠，像是 &lt;code&gt;0.0.0.0:11434&lt;/code&gt;，不過不建議更改連接埠，以維持最大的相容性。&lt;/p&gt;

&lt;h3&gt;
  
  
  Window 環境
&lt;/h3&gt;

&lt;p&gt;在安裝完 ollama 後，你可以利用以下指令查看目前 ollama 監聽的 IP：&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="n"&gt;netstat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ano&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;findstr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;":11434"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;輸出如下，可以看到目前只監聽本機迴路的 IP：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  TCP    127.0.0.1:3923         127.0.0.1:11434        TIME_WAIT       0
  TCP    127.0.0.1:11434        0.0.0.0:0              LISTENING       21544
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以透過測試連線來確認，首先取得本機的 IP 位址：&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="n"&gt;Get-NetIPAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IPAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InterfaceAlias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AddressFamily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddressFamily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'IPv4'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IPAddress      InterfaceAlias                     AddressFamily
---------      --------------                     -------------
172.19.144.1   vEthernet (WSL (Hyper-V firewall))          IPv4
169.254.130.47 區域連線* 3                                 IPv4
169.254.236.94 區域連線* 2                                 IPv4
169.254.221.5  藍牙網路連線                                IPv4
192.168.0.71   Wi-Fi                                       IPv4
127.0.0.1      Loopback Pseudo-Interface 1                 IPv4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;利用 curl 連線本機迴路測試：&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="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://127.0.0.1:11434/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可正常運作：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但如果利用 Wi-Fi IP 連線：&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="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://192.168.0.71:11434/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;就會無法連線：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl: (7) Failed to connect to 192.168.0.71 port 11434 after 2032 ms: Could not connect to server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;為了讓 ollama 可以接受本機迴路以外的連線，你可以在使用者或是系統環境變數加入 &lt;code&gt;OLLAMA_HOST&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpsfsv5b5wlq7g61w53j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpsfsv5b5wlq7g61w53j.png" alt=" " width="417" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;重新登入或是重新啟動後即可確認環境變數：&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;OLLAMA_HOST&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11434&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再透過指令確認目前監聽的狀況：&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="n"&gt;netstat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ano&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;findstr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;":11434"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;輸出如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  TCP    0.0.0.0:11434          0.0.0.0:0              LISTENING       11832
  TCP    [::]:11434             [::]:0                 LISTENING       11832
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;即可看到監聽了所有的 IP，如果用 curl 測試：&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="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://192.168.0.71:11434/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;會看到可以正常連線。&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux/MacOS 下手動啟動
&lt;/h3&gt;

&lt;p&gt;如果你沒有把 ollama 安裝成自動啟動的服務，就只要在使用者自己的 .bashrc 或是 .zshrc、.profile 檔中匯出環境變數即可，以下先取得本機 IP 位址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show eth0 | &lt;span class="nb"&gt;grep &lt;/span&gt;inet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以看到本機的 IP 是 172.19.149.141：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet 172.19.149.141/20 brd 172.19.159.255 scope global eth0
inet6 fe80::215:5dff:fe40:a05c/64 scope link 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;透過 curl 可以測試是否可連線：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl http://localhost:11434/
Ollama is running

curl http://172.19.149.141:11434/
curl: (7) Failed to connect to 172.19.149.141 port 11434 after 0 ms: Couldn't connect to server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著先把 ollama 停掉：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkill ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果只是要短暫接受非本機連線，也可以直些設定環境變數，例如：&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="nv"&gt;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 ollama serve &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在不論是本機或是非本機的 IP 都可以正常連線了：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/
Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://172.19.149.141:11434/
Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果希望每次啟動 ollama 都會接收非本機的連線，就可以在 .bashrc、.profile 等 shell 的設定檔中加入：&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;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以達到一樣的效果，重新啟動 ollama 後，我從 Windows 連過去測試：&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://172.19.149.141:11434/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在就通行無阻了。&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux 下使用 systemctl 啟動的服務
&lt;/h3&gt;

&lt;p&gt;如果是以 &lt;code&gt;systemctl&lt;/code&gt; 啟動服務，必須到對應的 service 檔中設定環境變數，你可以在 /etc/systemd/system 或是 /usr/lib/systemd/system 下找到 ollama.service 檔：&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;cat&lt;/span&gt; /etc/systemd/system/ollama.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;底下是我自己的範例，請在 &lt;code&gt;[Service]&lt;/code&gt; 區段中找到 Environment 項目，以 &lt;code&gt;"key=value"&lt;/code&gt; 的格式加入環境變數，我在這裡加入了 "OLLAMA_HOST=0.0.0.0:11434" 讓 ollama 會監聽所有的 IP。：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;連接埠 11434 是預設值，可以省略。&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Ollama Service&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/ollama serve&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;
&lt;span class="py"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"PATH=&amp;lt;安裝時使用者的 PATH 路徑設定&amp;gt;"&lt;/span&gt; &lt;span class="s"&gt;"OLLAMA_HOST=0.0.0.0:11434"&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你也可以把 Environment 項目分成多行撰寫：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"PATH=&amp;lt;安裝時使用者的 PATH 路徑設定&amp;gt;"&lt;/span&gt; 
&lt;span class="s"&gt;Environment="OLLAMA_HOST=0.0.0.0:11434"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;注意：在 Linux 中，重新安裝是更新 Ollama 的唯一方法，但是重新安裝會蓋掉上述服務設定檔，所以請記得更新版本後，要重新修改設定檔，否則就無法從遠端連入使用 Ollama 了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;修改完存檔後必須重新啟動服務才會生效，重新啟動前：&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;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;net-tools
&lt;span class="nb"&gt;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-anp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;會看到只監聽 &lt;code&gt;128.0.0.1&lt;/code&gt; 這個 IP 位址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tcp        0      0 127.0.0.1:11434         0.0.0.0:*               LISTEN      120/ollama          
unix  3      [ ]         STREAM     CONNECTED     1342     120/ollama  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著重新啟動：&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;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認是否有生效：&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;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-anp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;輸出如下，現在監聽的是 &lt;code&gt;:::11434&lt;/code&gt;，沒有限定只有本機迴路的 IP 了：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tcp6       0      0 :::11434                :::*                    LISTEN      1092/ollama 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在除了以 localhost 或是 127.0.0.1 連接 ollama 外，也可以使用本機對外的 IP 位址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show eth0 | &lt;span class="nb"&gt;grep &lt;/span&gt;inet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可看到像是如下的輸出：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet 172.19.149.141/20 brd 172.19.159.255 scope global eth0
inet6 fe80::215:5dff:fead:ec41/64 scope link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;即可使用該 IP 測試：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://172.19.149.141:11434/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;看到以下訊息就表示成功：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你也可以到 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="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://172.19.149.141:11434/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;一樣可以取得同樣的結果：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：Ollama 目前在使用者介面上已經有 &lt;strong&gt;Expose OLLAMA to the network&lt;/strong&gt; 選項可以切換是否要讓外部連入，不用撰寫服務設定檔了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mac 下比較特別：首先，在 .zshrc 等 shell 相關設定檔中設定的環境變數只對 shell 有效，但是 ollama 在 Mac 上是在使用者登入後透過帳戶的&lt;strong&gt;登入項目與延伸功能&lt;/strong&gt;的設定啟動 ollama 桌面程式（也就是你在工作列看到的小圖示），並且在 ollama 桌面程式的資源檔中設定執行 &lt;code&gt;ollama serve&lt;/code&gt; 指令啟動 ollama 服務，因此在 shell 的設定檔中所設定的環境變數完全不會生效。&lt;/p&gt;

&lt;p&gt;為了讓 ollama 服務啟動時可以依據環境變數接受非本機連線，我們必須改成使用 &lt;a href="https://www.launchd.info/" rel="noopener noreferrer"&gt;launchd&lt;/a&gt; 機制啟動 ollama 服務。首先，必須在 &lt;code&gt;~/Library/LaunchAgents/&lt;/code&gt; 建立一個&lt;code&gt;com.ollama.serve.plist&lt;/code&gt; 服務描述檔：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.ollama.serve&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/Applications/Ollama.app/Contents/Resources/ollama&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;serve&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;EnvironmentVariables&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;OLLAMA_HOST&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;0.0.0.0:11434&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RunAtLoad&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;KeepAlive&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Label&lt;/strong&gt; 鍵：此服務的名稱。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProgramArgs&lt;/strong&gt; 鍵：此服務要執行的指令。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EnvironmentVariables&lt;/strong&gt; 鍵：要提供給此服務執行時的環境變數&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;這個服務會在此帳號的使用者登入時啟動，要讓這個服務生效，必須先把已經在執行的 ollama 服務關閉：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;killall ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;然後使用 &lt;a href="https://ss64.com/mac/launchctl.html" rel="noopener noreferrer"&gt;launchctl&lt;/a&gt; 指令載入剛剛寫好的設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchctl load ~/Library/LaunchAgents/com.ollama.serve.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;載入後，就可以測試看看，先取得 IP：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ipconfig getifaddr en0
192.168.0.37
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再利用 curl 工具測試：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://192.168.0.37:11434
Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認可以正常運作之後，還有最重要的一步，移除原本 ollama 桌面程式在登入後自動執行並啟動 ollama 服務的設定，否則 launchd 啟動服務時會因為已經有在監聽本機迴路的 ollama 而失敗：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw9o8shjew5fzw70ukfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw9o8shjew5fzw70ukfo.png" alt=" " width="716" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;移除 ollama 項目後，之後重新開機登入此帳號，就會自動啟動剛剛設定的服務了。&lt;/p&gt;

&lt;p&gt;之後若是有修改服務的設定檔，就要先從系統中卸載之前載入的設定檔：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchctl unload ~/Library/LaunchAgents/com.ollama.serve.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再重新載入設定檔即可。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 ollama 指令連接遠端的 ollama 服務
&lt;/h2&gt;

&lt;p&gt;只要設定 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 環境變數為遠端 ollama 服務的位址，就可以連接遠端 ollama 服務，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;沒有設定 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 時，這是本機上 ollama 安裝的模型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                                   ID              SIZE      MODIFIED    
weitsung50110/llama-3-taiwan:8b-instruct-dpo-q4_K_M    7fcf06fa5eaa    4.9 GB    5 days ago     
weitsung50110/multilingual-e5-large-instruct:f16       ad6d1b04ec00    1.1 GB    2 weeks ago    
gemma3:latest                                          a2af6cc3eb7f    3.3 GB    2 weeks ago    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;我們可以先設定環境變數（以下以 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="nv"&gt;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.37 ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在看到的則是我在 192.168.0.37 位址的 ollama 安裝的模型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                                   ID              SIZE      MODIFIED       
gemma3:4b                                              a2af6cc3eb7f    3.3 GB    38 minutes ago    
weitsung50110/multilingual-e5-large-instruct:f16       ad6d1b04ec00    1.1 GB    3 hours ago       
weitsung50110/llama-3-taiwan:8b-instruct-dpo-q4_K_M    7fcf06fa5eaa    4.9 GB    3 hours ago       
gemma3:12b                                             f4031aab637d    8.1 GB    24 hours ago      
gemma3:1b                                              8648f39daa8f    815 MB    24 hours ago      
Darrrrr/mymodel:latest                                 40a13e3b82e2    815 MB    12 days ago       
deepseek-r1:14b                                        ea35dfe18182    9.0 GB    12 days ago       
qwq:latest                                             009cb3f08d74    19 GB     2 weeks ago       
gemma3:27b                                             a418f5838eaf    17 GB     2 weeks ago       
deepseek-r1:70b                                        0c1615a8ca32    42 GB     2 weeks ago       
deepseek-r1:32b                                        38056bbcbb2d    19 GB     2 weeks ago    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這樣一來，你就可以彈性的配置實際要跑 ollama 模型的機器，其他的機器則只要透過遠端連線就可以使用同一組模型了。如果需要，你也可以直接在系統或是 shell 的設定檔中設定環境變數，就不需要每次執行都要先設定環境變數了。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 API 連接遠端的 ollama
&lt;/h2&gt;

&lt;p&gt;從遠端利用 API 使用 ollama：只要透過 API 位址的參數指定遠端 IP 即可，本例遠端 ollama 服務位於 &lt;code&gt;192.168.0.37&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://192.168.0.37:11434/api/chat &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
&amp;gt;   "model": "gemma3:4b",
&amp;gt;   "messages": [
&amp;gt;     {
&amp;gt;       "role": "user",
&amp;gt;       "content": "你好"
&amp;gt;     }
&amp;gt;   ],
&amp;gt;   "stream": false
&amp;gt; }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以取得正確結果：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"model":"gemma3:4b","created_at":"2025-04-30T09:48:53.946791Z","message":{"role":"assistant","content":"你好！很高兴和你聊天。有什么我可以帮助你的吗？\n"},"done_reason":"stop","done":true,"total_duration":361840209,"load_duration":57624667,"prompt_eval_count":10,"prompt_eval_duration":79502667,"eval_count":15,"eval_duration":224293500}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用 ollama 套件則是指定 &lt;code&gt;host&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncClient&lt;/span&gt;

&lt;span class="k"&gt;async&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="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://192.168.0.37:11434&lt;/span&gt;&lt;span class="sh"&gt;'&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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;gemma3:4b&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&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="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;回應:&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&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;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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;若要使用 OpenAI API，必須傳入遠端 ollama 伺服器的位址連同&lt;strong&gt;連接埠&lt;/strong&gt;（預設為 &lt;code&gt;11434&lt;/code&gt;）給 &lt;code&gt;base_url&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;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="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="c1"&gt;# 配置遠端 Ollama API
&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_API_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;http://192.168.0.37:11434/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# 遠端 Ollama 伺服器
# OLLAMA_API_URL = "http://127.0.0.1:11434/v1"  # 本機 Ollama 伺服器
&lt;/span&gt;&lt;span class="n"&gt;MODEL_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;gemma3:12b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 初始化 OpenAI 客戶端，指向 Ollama 的 API
&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;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&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;MODEL_NAME&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ollama</category>
    </item>
    <item>
      <title>讓 Claude Code 串接 Ollama 使用本地端模型</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Mon, 19 Jan 2026 06:43:34 +0000</pubDate>
      <link>https://dev.to/codemee/rang-claude-code-chuan-jie-ollama-shi-yong-ben-di-duan-mo-xing-5aph</link>
      <guid>https://dev.to/codemee/rang-claude-code-chuan-jie-ollama-shi-yong-ben-di-duan-mo-xing-5aph</guid>
      <description>&lt;p&gt;Ollama v0.14.0 後提供了&lt;a href="https://docs.ollama.com/api/anthropic-compatibility" rel="noopener noreferrer"&gt;與 Anthropic API 的相容性&lt;/a&gt;，這使得原本串接 Antropic API 的程式碼可以只接改用 Ollama 上的本地或雲端模型，同時也讓 &lt;a href="https://ollama.com/blog/claude" rel="noopener noreferrer"&gt;Claude Code 可以改用 Ollama 上的本地或地端模型&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;要讓 Claude Code 接上 Ollama，只要設定以下兩個環境變數：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ANTHROPIC_AUTH_TOKEN=ollama
export ANTHROPIC_BASE_URL=http://&amp;lt;你的 Ollama 伺服器網址&amp;gt;:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows 請改成：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$env:ANTHROPIC_AUTH_TOKEN="ollama"
$env:export ANTHROPIC_BASE_URL="http://&amp;lt;你的 Ollama 伺服器網址&amp;gt;:11434"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果要使用的是同一機器上的 Ollama 伺服器，伺服器網址寫 "localhost" 或是 "127.0.0.1" 都可以。如果是要使用其他台機上的 Ollama 伺服器，記得依照這篇&lt;a href="https://dev.to/codemee/yuan-duan-shi-yong-ollama-de-fang-fa-2j5g"&gt;遠端使用 ollama 的方法&lt;/a&gt;，讓 Ollama 伺服器可以接受遠端連線。&lt;/p&gt;

&lt;p&gt;以我自己的環境為例，Ollama 伺服器位於區網上的 192.168.0.150，安裝的模型如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ ollama list
NAME                 ID              SIZE      MODIFIED
gemma3:27b           a418f5838eaf    17 GB     3 weeks ago
gemma3:12b-it-qat    5d4fa005e7bb    8.9 GB    2 months ago
gpt-oss:120b         a951a23b46a1    65 GB     2 months ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;就可以如下執行 Claude Code：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ claude --model gpt-oss:120b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;實際測試如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; 你好

● 你好！有什么我可以帮助你的吗？

✻ Crunched for 56s

&amp;gt; 你現在使用哪一種模型？

● 我目前使用的是 gpt-oss:120b 模型。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以下則是 &lt;code&gt;/model&lt;/code&gt; 的輸出：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Select model
 Switch between Claude models. Applies to this session and future Claude Code sessions. For other/previous model
 names, specify with --model.

   1. Default (recommended)  Use the default model (currently Sonnet 4.5) · $3/$15 per Mtok
   2. Opus                   Opus 4.5 · Most capable for complex work · $5/$25 per Mtok
   3. Haiku                  Haiku 4.5 · Fastest for quick answers · $1/$5 per Mtok
 &amp;gt; 4. gpt-oss:120b √         Custom model

 Enter to confirm · escape to exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;除了本機端的模型，你也可以使用 Ollama 雲端的模型，例如以下先拉取所需的雲端模型資訊：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ ollama pull minimax-m2.1:cloud
pulling manifest 
pulling 692f7c2439cc: 100% ▕█████████████████████████████████████████████████████████▏  378 B                         
verifying sha256 digest 
writing manifest 
success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;然後讓 Ollama 伺服器登入你的帳號以便取用雲端模型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ ollama signin
You need to be signed in to Ollama to run Cloud models.

To sign in, navigate to:
    https://ollama.com/connect?name=ubuntu&amp;amp;key=c3NoLWVk...3NlYno
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;完成後即可使用剛剛拉取下來的雲端模型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ claude --model minimax-m2.1:cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;實測結果如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; 你好

✽ Beboppin'… (ctrl+c to interrupt)

● 你好！有什麼我可以幫你的嗎？

&amp;gt; 你使用哪一種模型？

● 我使用的是 minimax-m2.1:cloud 模型
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>claudecode</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Claude Code MCPSearch 工具</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Sat, 17 Jan 2026 04:28:16 +0000</pubDate>
      <link>https://dev.to/codemee/claude-code-mcpsearch-gong-ju-409e</link>
      <guid>https://dev.to/codemee/claude-code-mcpsearch-gong-ju-409e</guid>
      <description>&lt;p&gt;Claude Code 在 &lt;a href="https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#217" rel="noopener noreferrer"&gt;2.1.7&lt;/a&gt; 版時改成預設啟用 &lt;strong&gt;MCPSearch&lt;/strong&gt; 工具，它會在你的 MCP 工具資訊佔用的 token 數達到 context window 的 &lt;strong&gt;10%（預設值）&lt;/strong&gt; 時干預 MCP 工具的使用方式，最主要有兩個效應：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;延後把 MCP 工具的資訊加入 contex 的時間&lt;/strong&gt;：只有在模型判斷目前資訊無法完成任務時，才會使用 MCPSearch 工具搜尋是否有適當的 MCP 工具可用，這時才會根據 MCPSearch 工具的搜尋結果載入 MCP 工具的資訊。優點就是在不需要使用 MCP 工具的情況下，不論你連接了多少個 MCP 伺服器，都完全不佔用 contex window。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;只會提供所需工具的資訊&lt;/strong&gt;，不會把所屬 MCP 伺服器的所有工具的資訊都加入 context。也就是說，如果某個 MCP 伺服器提供有 10 個工具，但只需要用到其中一項工具時，就只會提供給模型該項工具的資訊參考，不會把 10 個工具的資訊都放入 context 中，無端佔用額外的 contex window。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有興趣可以參考 Claude 中相對應的 &lt;a href="https://www.anthropic.com/engineering/advanced-tool-use#tool-search-tool" rel="noopener noreferrer"&gt;Tool Search 工具&lt;/a&gt; 的說明。&lt;/p&gt;

&lt;h2&gt;
  
  
  以 Notion MCP 伺服器實測
&lt;/h2&gt;

&lt;p&gt;你很容易就可以發現為什麼要預設啟用 MCPSearch 工具，以 Notion 官方的 MCP 伺服器為例，即使我的 Claude Code 只連接了這一個 MCP 伺服器：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ claude mcp list
Checking MCP server health...

notion: https://mcp.notion.com/mcp (HTTP) - ✓ Connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;當你以 &lt;code&gt;/context&lt;/code&gt; 查看時：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Context Usage
⛁ ⛀ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁   claude-opus-4-5-20251101 · 41k/200k tokens (21%)
⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁   ⛁ System prompt: 3.0k tokens (1.5%)
⛀ ⛀ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System tools: 17.0k tokens (8.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ MCP tools: 21.1k tokens (10.6%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ Messages: 8 tokens (0.0%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛶ Free space: 114k (56.9%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛝ Autocompact buffer: 45.0k tokens (22.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛝ ⛝ ⛝
⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝
⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝

MCP tools · /mcp
└ mcp__notion__notion-search: 1.5k tokens
└ mcp__notion__notion-fetch: 304 tokens
└ mcp__notion__notion-create-pages: 2.0k tokens
└ mcp__notion__notion-update-page: 2.0k tokens
└ mcp__notion__notion-move-pages: 611 tokens
└ mcp__notion__notion-duplicate-page: 220 tokens
└ mcp__notion__notion-create-database: 5.8k tokens
└ mcp__notion__notion-update-database: 6.0k tokens
└ mcp__notion__notion-create-comment: 1.6k tokens
└ mcp__notion__notion-get-comments: 108 tokens
└ mcp__notion__notion-get-teams: 229 tokens
└ mcp__notion__notion-get-users: 452 tokens
└ mcp__notion__notion-get-self: 79 tokens
└ mcp__notion__notion-get-user: 120 tokens                                 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;會發現光是 Notion 提供的 MCP 工具就有 14 個，總共耗用 21.1k 個 token，比所有系統（內建）工具的 17.0k 還多。當然，這樣的好處是模型可參考所有工具的資訊，直接挑選適當的使用，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; 我有哪些跟提案相關的筆記？

● notion - notion-search (MCP)(query: "提案", query_type: "internal")

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Tool use

   notion - notion-search(query: "提案", query_type: "internal") (MCP)
   Perform a search over:
...（略）
 Do you want to proceed?
 &amp;gt; 1. Yes
   2. Yes, and don't ask again for notion - notion-search commands in C:\Users\meebo\code\tt
   3. No

 Esc to cancel · Tab to add additional instruction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你可以看到模型根據問題，馬上找出可以使用 Notion 提供的搜尋工具協助處理，但壞處就是如果提問內容根本不需要 MCP 工具的協助，還是得佔用這 21.1k 個 token 的空間，白白燒掉 token 額度。&lt;/p&gt;

&lt;p&gt;我們沒有要真的測試 Notion 的工具效果，所以這裡先中斷離開 Claude Code。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 MCPSearch 工具
&lt;/h2&gt;

&lt;p&gt;接著來看看如果啟用 MCPSearch 工具，會有什麼差別？要特別注意的是，雖然官方文件說預設會啟用 MCPSearch 工具，不過我自己的測試環境中，是已經升級到 2.1.11 的版本，但並不會自動啟用 MCPSearch 工具：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ claude --version
2.1.11 (Claude Code)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在 Claude Code 的 &lt;a href="https://github.com/anthropics/claude-code/issues" rel="noopener noreferrer"&gt;Github issue&lt;/a&gt; 頁面中，找到其他人類似的問題，提供了&lt;a href="https://github.com/anthropics/claude-code/issues/18370#issuecomment-3756355289" rel="noopener noreferrer"&gt;繞路的解法&lt;/a&gt;，設定環境變數 &lt;code&gt;ENABLE_TOOL_SEARCH&lt;/code&gt; 為 &lt;code&gt;true&lt;/code&gt; 就可以強制啟用 MCPSearch 工具，底下是我在 Windows 的 PowerShell 下測試：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ $env:ENABLE_TOOL_SEARCH="true"; claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果使用 macOS/Linux，就改用：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENABLE_TOOL_SEARCH=true claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果覺得麻煩，也可以在 .claude/settings.json 檔中加入環境變數的設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ENABLE_TOOL_SEARCH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;執行後一樣使用 &lt;code&gt;/context&lt;/code&gt; 觀察目前的 contex 用量：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Context Usage
⛁ ⛀ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁   claude-opus-4-5-20251101 · 20k/200k tokens (10%)
⛀ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System prompt: 3.1k tokens (1.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System tools: 17.0k tokens (8.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ Messages: 8 tokens (0.0%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛶ Free space: 135k (67.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛝ Autocompact buffer: 45.0k tokens (22.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛝ ⛝ ⛝
⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝
⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝

MCP tools · /mcp (loaded on-demand)

Available
└ mcp__notion__notion-search
└ mcp__notion__notion-fetch
└ mcp__notion__notion-create-pages
└ mcp__notion__notion-update-page
└ mcp__notion__notion-move-pages
└ mcp__notion__notion-duplicate-page
└ mcp__notion__notion-create-database
└ mcp__notion__notion-update-database
└ mcp__notion__notion-create-comment
└ mcp__notion__notion-get-comments
└ mcp__notion__notion-get-teams
└ mcp__notion__notion-get-users
└ mcp__notion__notion-get-self
└ mcp__notion__notion-get-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你可以看到兩個主要的差異：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在上方的分類 token 用量統計中，原本佔用 21.1k 個 token 的 MCP tools 不見了，總用量也從原本看到的 41k 驟降成 20k，少掉的就是 MCP Tools 的 21.1k。&lt;/li&gt;
&lt;li&gt;在底下標示 &lt;strong&gt;MCP tools&lt;/strong&gt; 的區域，出現了 &lt;strong&gt;"（loaded on-emand）"&lt;/strong&gt; 字樣，表示現在是依據需要才會載入 MCP 工具的資訊到 context 中。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;很顯然 MCPSearch 生效了，不確定會需要使用 MCP 工具時，就不會把 MCP 工具放入 context 中。接著就來驗證看看：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; 我有哪些跟提案相關的筆記？

● 讓我搜尋你的 Notion 筆記中與提案相關的內容。

● MCPSearch(Search MCP tools: "select:mcp__notion__notion-search")
  ⎿ &amp;nbsp;Found 1 tool

● notion - notion-search (MCP)(query: "提案", query_type: "internal")

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Tool use

   notion - notion-search(query: "提案", query_type: "internal") (MCP)
...（略）
 Do you want to proceed?
 &amp;gt; 1. Yes
   2. Yes, and don't ask again for notion - notion-search commands in C:\Users\meebo\code\tt
   3. No

 Esc to cancel · Tab to add additional instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;我們問了同樣的問題，你會看到最終模型還是會使用 Notion 伺服器提供的搜尋工具，但是過程不大一樣：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;模型先使用了 MCPSearch 工具，它的執行結果是 ""select:mcp_&lt;em&gt;notion&lt;/em&gt;_notion-search")"，告訴模型可以選用 Notion 的搜尋工具。&lt;/li&gt;
&lt;li&gt;接著模型依據 MCPSeach 工具的搜尋結果使用 Notion 的搜尋工具。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;事情還不只如此，即使現在使用到了 Notion 伺服器提供的工具，如果中斷剛剛的交談，再使用 &lt;code&gt;/context&lt;/code&gt; 查看用量：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Context Usage
⛁ ⛀ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁   claude-opus-4-5-20251101 · 22k/200k tokens (11%)
⛁ ⛀ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System prompt: 3.0k tokens (1.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System tools: 17.0k tokens (8.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ MCP tools: 1.5k tokens (0.8%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ Messages: 685 tokens (0.3%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛶ Free space: 133k (66.4%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛝ Autocompact buffer: 45.0k tokens (22.5%)
⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛝ ⛝ ⛝
⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝
⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝

MCP tools · /mcp (loaded on-demand)

Loaded
└ mcp__notion__notion-search: 1.5k tokens

Available
└ mcp__notion__notion-fetch
└ mcp__notion__notion-create-pages
└ mcp__notion__notion-update-page
└ mcp__notion__notion-move-pages
└ mcp__notion__notion-duplicate-page
└ mcp__notion__notion-create-database
└ mcp__notion__notion-update-database
└ mcp__notion__notion-create-comment
└ mcp__notion__notion-get-comments
└ mcp__notion__notion-get-teams
└ mcp__notion__notion-get-users
└ mcp__notion__notion-get-self
└ mcp__notion__notion-get-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你會看到兩個不同的地方：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;上方分類統計用量的地方出現了 &lt;strong&gt;MCP tools&lt;/strong&gt;，但是總量是 1.5k，不是最前面看到的 21.1k，表示並不是所有 Notion 提供的工具資訊都放入了 context。&lt;/li&gt;
&lt;li&gt;在下方標示 &lt;strong&gt;MCP tools&lt;/strong&gt; 的地方，會看到多了 &lt;strong&gt;Loaded&lt;/strong&gt; 區塊，這裡只列出了 &lt;code&gt;mcp__notion__notion-search&lt;/code&gt;，佔用 1.5k 個 token。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;正因為如此，所以雖然用到了 MCP 工具，但是 context 總量也還是只有 22k 而已，不會激增到最前面看到的 41k。&lt;/p&gt;

&lt;h2&gt;
  
  
  MCPSearch 工具的代價
&lt;/h2&gt;

&lt;p&gt;MCPSearch 工具的基本概念很簡單，它想做到的就是像 &lt;a href="https://agentskills.io/what-are-skills" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; 那樣，只有在需要的時候才提供完整資訊。如果 MCP 工具使用到的機率不高，廣度也不大，也就是不會一下要用 A 工具、一下要用 B 工具，一下又要用 C 工具，那麼沒有用到 MCP 工具時所省下的 token 量，應該可以讓你節省很多額度，代價則是在新的交談階段初次使用到特定的 MCP 工具前，都會先使用一次 MCPSearch 工具，多進行一次交談。&lt;/p&gt;

&lt;p&gt;從上面的測試也可以看到，就算只連接了一個 MCP 伺服器，但如果提供的工具繁多，或是該工具比較複雜，那麼相關的資訊量也是可以多到你嚇一跳的。單單一個 Notion 伺服器提供的工具就佔了 21.1k 個 token，整個 context window 也才 200k，10%↑ 的量就不見了，是很有感的差異，更別說你可能連接了不只一個 MCP 伺服器。&lt;/p&gt;

&lt;p&gt;當然，如果你就只會連接佔用量很少的 MCP 伺服器，整體用量可能比多一輪問答使用 MCPSearch 工具的 token 量（這會包含交談記錄、系統訊息、系統工具）少很多，或者使用到 MCP 工具的機率非常頻繁，那就可以權衡是否要啟用 MCPSearch 工具。&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>claude</category>
      <category>mcp</category>
      <category>mcpsearch</category>
    </item>
    <item>
      <title>VScode 的自動執行機制--Tasks</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Mon, 12 Jan 2026 04:13:37 +0000</pubDate>
      <link>https://dev.to/codemee/vscode-de-zi-dong-zhi-xing-ji-zhi-tasks-387j</link>
      <guid>https://dev.to/codemee/vscode-de-zi-dong-zhi-xing-ji-zhi-tasks-387j</guid>
      <description>&lt;p&gt;由於看到一篇&lt;a href="https://www.threads.com/@haocherhong/post/DTPM7njkY-T?xmt=AQF0HNlVISd5EVEg8w_Oq54PRhVkeP9-z9h2SoaMy77kSJo" rel="noopener noreferrer"&gt;科技詐騙的文章&lt;/a&gt;，才知道 VSCode 開啟資料夾時問的『是否信任此資料夾』原來很重要。這個詐騙成功的關鍵首先是 VSCode 執行&lt;a href="https://code.visualstudio.com/docs/debugtest/tasks" rel="noopener noreferrer"&gt;任務&lt;/a&gt;的機制，原始的目的是方便大家設計好可以建置環境、編譯、測試專案的指令，不需要自己再去終端機敲長串的指令，但要命的是這個機制可以設定成 VSCode 開啟資料夾時就&lt;a href="https://code.visualstudio.com/docs/debugtest/tasks#_run-behavior" rel="noopener noreferrer"&gt;自動執行&lt;/a&gt;，對於從像是 GutHub 複製下來的專案，增添了極高的風險。&lt;/p&gt;

&lt;h2&gt;
  
  
  自動執行任務的機制
&lt;/h2&gt;

&lt;p&gt;VSCode 可以在 .vscode 下放置 tasks.json，格式如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;See&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://go.microsoft.com/fwlink/?LinkId=&lt;/span&gt;&lt;span class="mi"&gt;733558&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;documentation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tasks.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"play"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uv run main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"runOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"runOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"folderOpen"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt; 項目可以搭配一些開發環境設定，執行特定的開發工具。若設定為 "shell"，就可以自由執行任何指令。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runOptions&lt;/code&gt; 則可以用來設定執行選項，像是 &lt;code&gt;runOn&lt;/code&gt; 就可以設定為 "folderOpen"，只要用 VSCode 開啟該資料夾就會自動執行，這就可以用來執行自動清除快取之類的任務。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不過因為可以自動執行，所以如果埋了一個惡意程式，就可能會傷害你的電腦，甚至暗中安裝常駐的惡意程式之類。&lt;/p&gt;

&lt;p&gt;這個自動執行機制還可以透過 &lt;code&gt;presentation&lt;/code&gt; 屬性設定得更隱密：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;See&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://go.microsoft.com/fwlink/?LinkId=&lt;/span&gt;&lt;span class="mi"&gt;733558&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;documentation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tasks.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"play"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uv run main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"runOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"runOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"folderOpen"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"presentation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"reveal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"never"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"revealProblems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"never"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"close"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reveal&lt;/code&gt;：控制是否要顯示執行任務的終端機面板。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;revealProblem&lt;/code&gt;：則是控制執行有問題時，是否要顯示問題面板。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;close&lt;/code&gt;：控制執行完任務後是否要關閉終端機。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;只要設定都不顯示面板，而且執行後關閉終端機，除非程式本身開啟額外的視窗、或非常佔用資源等等，否則就很難發現它的蹤跡。&lt;/p&gt;

&lt;h2&gt;
  
  
  避免自動執行任務的危險
&lt;/h2&gt;

&lt;p&gt;要避免上述問題，最簡單的方式就是每次開啟新的資料夾時，都不要信任該資料夾。如果已經信任過資料夾，也可以按 Ctrl+Shift+C開啟&lt;strong&gt;命令選擇區&lt;/strong&gt;後執行 &lt;strong&gt;manage workspace trust&lt;/strong&gt; 命令進入設定頁面，再按&lt;strong&gt;不信任&lt;/strong&gt;，&lt;/p&gt;

&lt;p&gt;如果是因為父資料夾受信任而自動信任子資料夾，就要到父資料夾中設定。或者也可以在同一設定頁面下的&lt;strong&gt;信任的資料夾與工作區&lt;/strong&gt;中，從清單中移除已被信任的資料夾。&lt;/p&gt;

&lt;p&gt;如果需要信任資料夾的好處，不想動到資校夾或工作區的信任設定，也可以修改允許自動執行任務的設定，你可以在工作區的設定檔 .vscode/settings.json 中加上如下的設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"task.allowAutomaticTasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;就不會自動執行任務。你也可以加到使用者設定檔中，避免從網路下載 repo 後，一時忘記就直接開啟，釀成大禍。之後若真的需要執行特定的任務，可以到&lt;strong&gt;命令選擇區&lt;/strong&gt;中執行 &lt;strong&gt;Run Task&lt;/strong&gt; 命令，選擇要執行的任務即可。&lt;/p&gt;

&lt;p&gt;要注意的是，像是 Cursor 這個延伸自 VSCode 的編輯器，預設是不啟用信任機制的，也就是完全信任所有的資料夾，不可不小心。&lt;/p&gt;

</description>
      <category>vscode</category>
    </item>
    <item>
      <title>PowerShell pipeline chain operator 的小陷阱</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Fri, 12 Dec 2025 01:38:08 +0000</pubDate>
      <link>https://dev.to/codemee/powershell-pipeline-chain-operator-de-xiao-xian-jing-3d67</link>
      <guid>https://dev.to/codemee/powershell-pipeline-chain-operator-de-xiao-xian-jing-3d67</guid>
      <description>&lt;p&gt;PowerShell 7 提供有&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipeline_chain_operators?view=powershell-7.5" rel="noopener noreferrer"&gt;pipeline chain operator&lt;/a&gt;，也就是 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 與 &lt;code&gt;||&lt;/code&gt;，不過使用上必須注意，不然就會遇到意外的結果。&lt;/p&gt;

&lt;h2&gt;
  
  
  運算子的兩端都要是指令
&lt;/h2&gt;

&lt;p&gt;看到 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 與 &lt;code&gt;||&lt;/code&gt;，很容易就會寫出類似這樣的指令：&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="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不過這會得到以下錯誤訊息：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit: The term 'exit' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這是因為 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 與 &lt;code&gt;||&lt;/code&gt; 的左右兩邊都要是指令（包含 cmdlet、函式、腳本檔、外部程式等），但 &lt;code&gt;exit&lt;/code&gt; 是 PowerShell 的保留字，在語法上是陳述句，並不是指令，所以你可以看到錯誤訊息明確表示無法執行 &lt;code&gt;exit&lt;/code&gt;。&lt;/p&gt;

&lt;h2&gt;
  
  
  沒有 pipeline 的功能
&lt;/h2&gt;

&lt;p&gt;這組運算子名稱中的 "pipeline" 也很容易誤解，以為他們是有條件的 pipeline，例如，你可能會期望以下指令可以在 &lt;code&gt;\temp&lt;/code&gt;  資料夾存在的情況下，將資料夾內的檔案清單傳給右邊的 &lt;code&gt;measure-object&lt;/code&gt;：&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="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\temp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;➜&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Measure-Object&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但其實並不會，你會看到類似以下的結果：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Directory: C:\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----         2025/12/9 下午 04:23                F6722
d----        2025/12/11 下午 10:49                test_wt
d----        2025/12/11 下午 10:45                tmr
-a---         2025/5/22 下午 10:50            162 tomica.jpg
-a---         2025/3/18 下午 04:55         521370 train1.jpeg
-a---         2025/3/18 下午 04:55         879688 train2.jpeg
-a---         2025/8/29 下午 11:56       10824402 VVAICar_Test.apk

Count             : 0
Average           : 
Sum               : 
Maximum           : 
Minimum           : 
StandardDeviation : 
Property          : 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也就是 pipeline chain operator 只會串接指令，但不會傳送輸出結果，如果命名為 chain operator 好像比較適當。&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>pipeline</category>
    </item>
    <item>
      <title>遠端使用 ollama 的方法</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Fri, 01 Aug 2025 05:42:48 +0000</pubDate>
      <link>https://dev.to/codemee/yuan-duan-shi-yong-ollama-de-fang-fa-53d0</link>
      <guid>https://dev.to/codemee/yuan-duan-shi-yong-ollama-de-fang-fa-53d0</guid>
      <description>&lt;p&gt;ollama 除了可以在本機使用外，也可以從遠端連接，不過預設 ollama 只接受本機的連線，無法從遠端連接。&lt;/p&gt;

&lt;h2&gt;
  
  
  設定可接受遠端連線
&lt;/h2&gt;

&lt;p&gt;要讓 ollama 接受遠端連線，必須在啟動 ollama 服務前設定 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 環境變數為 &lt;code&gt;0.0.0.0&lt;/code&gt;，否則就只會接收本機連線。設定環境變數時可設定為 &lt;code&gt;0.0.0.0&lt;/code&gt; 保留預設連接埠 &lt;code&gt;11434&lt;/code&gt;，或是也可以同時指定連接埠，像是 &lt;code&gt;0.0.0.0:11434&lt;/code&gt;，不過不建議更改連接埠，以維持最大的相容性。&lt;/p&gt;

&lt;h3&gt;
  
  
  Window 環境
&lt;/h3&gt;

&lt;p&gt;在安裝完 ollama 後，你可以利用以下指令查看目前 ollama 監聽的 IP：&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="n"&gt;netstat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ano&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;findstr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;":11434"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;輸出如下，可以看到目前只監聽本機迴路的 IP：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  TCP    127.0.0.1:3923         127.0.0.1:11434        TIME_WAIT       0
  TCP    127.0.0.1:11434        0.0.0.0:0              LISTENING       21544
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以透過測試連線來確認，首先取得本機的 IP 位址：&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="n"&gt;Get-NetIPAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IPAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InterfaceAlias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AddressFamily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddressFamily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'IPv4'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IPAddress      InterfaceAlias                     AddressFamily
---------      --------------                     -------------
172.19.144.1   vEthernet (WSL (Hyper-V firewall))          IPv4
169.254.130.47 區域連線* 3                                 IPv4
169.254.236.94 區域連線* 2                                 IPv4
169.254.221.5  藍牙網路連線                                IPv4
192.168.0.71   Wi-Fi                                       IPv4
127.0.0.1      Loopback Pseudo-Interface 1                 IPv4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;利用 curl 連線本機迴路測試：&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="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://127.0.0.1:11434/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可正常運作：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但如果利用 Wi-Fi IP 連線：&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="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://192.168.0.71:11434/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;就會無法連線：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl: (7) Failed to connect to 192.168.0.71 port 11434 after 2032 ms: Could not connect to server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;為了讓 ollama 可以接受本機迴路以外的連線，你可以在使用者或是系統環境變數加入 &lt;code&gt;OLLAMA_HOST&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9qpsj9h4qse4rmmuddj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9qpsj9h4qse4rmmuddj.png" alt="圖片" width="417" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;重新登入或是重新啟動後即可確認環境變數：&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;OLLAMA_HOST&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11434&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再透過指令確認目前監聽的狀況：&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="n"&gt;netstat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ano&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;findstr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;":11434"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;輸出如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  TCP    0.0.0.0:11434          0.0.0.0:0              LISTENING       11832
  TCP    [::]:11434             [::]:0                 LISTENING       11832
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;即可看到監聽了所有的 IP，如果用 curl 測試：&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="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://192.168.0.71:11434/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;會看到可以正常連線。&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux/MacOS 下手動啟動
&lt;/h3&gt;

&lt;p&gt;如果你沒有把 ollama 安裝成自動啟動的服務，就只要在使用者自己的 .bashrc 或是 .zshrc、.profile 檔中繪出環境變數即可，以下先取得本機 IP 位址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show eth0 | &lt;span class="nb"&gt;grep &lt;/span&gt;inet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以看到本機的 IP 是 172.19.149.141：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet 172.19.149.141/20 brd 172.19.159.255 scope global eth0
inet6 fe80::215:5dff:fe40:a05c/64 scope link 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;透過 curl 可以測試是否可連線：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl http://localhost:11434/
Ollama is running

curl http://172.19.149.141:11434/
curl: (7) Failed to connect to 172.19.149.141 port 11434 after 0 ms: Couldn't connect to server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著先把 ollama 停掉：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkill ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果只是要短暫接受非本機連線，也可以直些設定環境變數，例如：&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="nv"&gt;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 ollama serve &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在不論是本機或是非本機的 IP 都可以正常連線了：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/
Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://172.19.149.141:11434/
Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果希望每次啟動 ollama 都會接收非本機的連線，就可以在 .bashrc、.profile 等 shell 的設定檔中加入：&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;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以達到一樣的效果，重新啟動 ollama 後，我從 Windows 連過去測試：&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://172.19.149.141:11434/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在就通行無阻了。&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux 下使用 systemctl 啟動的服務
&lt;/h3&gt;

&lt;p&gt;如果是以 &lt;code&gt;systemctl&lt;/code&gt; 啟動服務，必須到對應的 service 檔中設定環境變數，你可以在 /etc/systemd/system 或是 /usr/lib/systemd/system 下找到 ollama.service 檔：&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;cat&lt;/span&gt; /etc/systemd/system/ollama.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;底下是我我自己的範例，請在 &lt;code&gt;[Service]&lt;/code&gt; 區段中找到 Environment 項目，以 &lt;code&gt;"key=value"&lt;/code&gt; 的格式加入環境變數，我在這裡加入了 "OLLAMA_HOST=0.0.0.0:11434" 讓 ollama 會監聽所有的 IP。：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Ollama Service&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/ollama serve&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;
&lt;span class="py"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"PATH=/home/meebox/.local/bin:/home/meebox/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/mnt/c/users/meebo/scoop/apps/vscode/current/bin"&lt;/span&gt; &lt;span class="s"&gt;"OLLAMA_HOST=0.0.0.0:11434"&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;修改完存檔後必須重新啟動服務才會生效，重新啟動前：&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;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;net-tools
&lt;span class="nb"&gt;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-anp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;會看到只監聽 &lt;code&gt;128.0.0.1&lt;/code&gt; 這個 IP 位址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tcp        0      0 127.0.0.1:11434         0.0.0.0:*               LISTEN      120/ollama          
unix  3      [ ]         STREAM     CONNECTED     1342     120/ollama  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著重新啟動：&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;sudo &lt;/span&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認是否有生效：&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;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-anp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;輸出如下，現在監聽的是 &lt;code&gt;:::11434&lt;/code&gt;，沒有限定只有本機迴路的 IP 了：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tcp6       0      0 :::11434                :::*                    LISTEN      1092/ollama 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在除了以 localhost 或是 127.0.0.1 連接 ollama 外，也可以使用本機對外的 IP 位址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show eth0 | &lt;span class="nb"&gt;grep &lt;/span&gt;inet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可看到像是如下的輸出：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet 172.19.149.141/20 brd 172.19.159.255 scope global eth0
inet6 fe80::215:5dff:fead:ec41/64 scope link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;即可使用該 IP 測試：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://172.19.149.141:11434/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;看到以下訊息就表示成功：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;你也可以到 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="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://172.19.149.141:11434/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;一樣可以取得同樣的結果：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MacOS
&lt;/h3&gt;

&lt;p&gt;Mac 下比較特別：首先，在 .zshrc 等 shell 相關設定檔中設定的環境變數只對 shell 有效，但是 ollama 在 Mac 上是在使用者登入後透過帳戶的&lt;strong&gt;登入項目與延伸功能&lt;/strong&gt;的設定啟動 ollama 桌面程式（也就是你在工作列看到的小圖示），並且在 ollama 桌面程式的資源檔中設定執行 &lt;code&gt;ollama serve&lt;/code&gt; 指令啟動 ollama 服務，因此在 shell 的設定檔中所設定的環境變數完全不會生效。&lt;/p&gt;

&lt;p&gt;為了讓 ollama 服務啟動時可以依據環境變數接受非本機連線，我們必須改成使用 &lt;a href="https://www.launchd.info/" rel="noopener noreferrer"&gt;launchd&lt;/a&gt; 機制啟動 ollama 服務。首先，必須在 &lt;code&gt;~/Library/LaunchAgents/&lt;/code&gt; 建立一個&lt;code&gt;com.ollama.serve.plist&lt;/code&gt; 服務描述檔：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.ollama.serve&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/Applications/Ollama.app/Contents/Resources/ollama&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;serve&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;EnvironmentVariables&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;OLLAMA_HOST&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;0.0.0.0:11434&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RunAtLoad&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;KeepAlive&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Label&lt;/strong&gt; 鍵：此服務的名稱。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProgramArgs&lt;/strong&gt; 鍵：此服務要執行的指令。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EnvironmentVariables&lt;/strong&gt; 鍵：要提供給此服務執行時的環境變數&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;這個服務會在此帳號的使用者登入時啟動，要讓這個服務生效，必須先把已經在執行的 ollama 服務關閉：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;killall ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;然後使用 &lt;a href="https://ss64.com/mac/launchctl.html" rel="noopener noreferrer"&gt;launchctl&lt;/a&gt; 指令載入剛剛寫好的設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchctl load ~/Library/LaunchAgents/com.ollama.serve.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;載入後，就可以測試看看，先取得 IP：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ipconfig getifaddr en0
192.168.0.37
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再利用 curl 工具測試：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://192.168.0.37:11434
Ollama is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認可以正常運作之後，還有最重要的一步，移除原本 ollama 桌面程式在登入後自動執行並啟動 ollama 服務的定，否則 launchd 啟動服務時會因為已經有在監聽本機迴路的 ollama 而失敗：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6nfiarrvfndikrn4uaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6nfiarrvfndikrn4uaa.png" alt="圖片" width="716" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;移除 ollama 項目後，之後重新開機登入此帳號，就會自動啟動剛剛設定的服務了。&lt;/p&gt;

&lt;p&gt;之後若是有修改服務的設定檔，就要先從系統中卸載之前載入的設定檔：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchctl unload ~/Library/LaunchAgents/com.ollama.serve.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再重新載入設定檔即可。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 ollama 指令連接遠端的 ollama 服務
&lt;/h2&gt;

&lt;p&gt;只要設定 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 環境變數為遠端 ollama 服務的位址，就可以連接遠端 ollama 服務，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;沒有設定 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 時，這是本機上 ollama 安裝的模型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                                   ID              SIZE      MODIFIED    
weitsung50110/llama-3-taiwan:8b-instruct-dpo-q4_K_M    7fcf06fa5eaa    4.9 GB    5 days ago     
weitsung50110/multilingual-e5-large-instruct:f16       ad6d1b04ec00    1.1 GB    2 weeks ago    
gemma3:latest                                          a2af6cc3eb7f    3.3 GB    2 weeks ago    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;我們可以先設定環境變數（以下以 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="nv"&gt;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.37 ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現在看到的則是我在 192.168.0.37 位址的 ollama 安裝的模型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                                   ID              SIZE      MODIFIED       
gemma3:4b                                              a2af6cc3eb7f    3.3 GB    38 minutes ago    
weitsung50110/multilingual-e5-large-instruct:f16       ad6d1b04ec00    1.1 GB    3 hours ago       
weitsung50110/llama-3-taiwan:8b-instruct-dpo-q4_K_M    7fcf06fa5eaa    4.9 GB    3 hours ago       
gemma3:12b                                             f4031aab637d    8.1 GB    24 hours ago      
gemma3:1b                                              8648f39daa8f    815 MB    24 hours ago      
Darrrrr/mymodel:latest                                 40a13e3b82e2    815 MB    12 days ago       
deepseek-r1:14b                                        ea35dfe18182    9.0 GB    12 days ago       
qwq:latest                                             009cb3f08d74    19 GB     2 weeks ago       
gemma3:27b                                             a418f5838eaf    17 GB     2 weeks ago       
deepseek-r1:70b                                        0c1615a8ca32    42 GB     2 weeks ago       
deepseek-r1:32b                                        38056bbcbb2d    19 GB     2 weeks ago    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這樣一來，你就可以彈性的配置實際要跑 ollama 模型的機器，其他的機器則只要透過遠端連線就可以使用同一組模型了。如果需要，你也可以直接在系統或是 shell 的設定檔中設定環境變數，就不需要每次執行都要先設定環境變數了。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 API 連接遠端的 ollama
&lt;/h2&gt;

&lt;p&gt;從遠端利用 API 使用 ollama：只要透過 API 位址的參數指定遠端 IP 即可，本例遠端 ollama 服務位於 &lt;code&gt;192.168.0.37&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://192.168.0.37:11434/api/chat &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
&amp;gt;   "model": "gemma3:4b",
&amp;gt;   "messages": [
&amp;gt;     {
&amp;gt;       "role": "user",
&amp;gt;       "content": "你好"
&amp;gt;     }
&amp;gt;   ],
&amp;gt;   "stream": false
&amp;gt; }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以取得正確結果：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"model":"gemma3:4b","created_at":"2025-04-30T09:48:53.946791Z","message":{"role":"assistant","content":"你好！很高兴和你聊天。有什么我可以帮助你的吗？\n"},"done_reason":"stop","done":true,"total_duration":361840209,"load_duration":57624667,"prompt_eval_count":10,"prompt_eval_duration":79502667,"eval_count":15,"eval_duration":224293500}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用 ollama 套件則是指定 &lt;code&gt;host&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncClient&lt;/span&gt;

&lt;span class="k"&gt;async&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="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://192.168.0.37:11434&lt;/span&gt;&lt;span class="sh"&gt;'&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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;gemma3:4b&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&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="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;回應:&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&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;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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;若要使用 OpenAI API，必須傳入遠端 ollama 伺服器的位址連同&lt;strong&gt;連接埠&lt;/strong&gt;（預設為 &lt;code&gt;11434&lt;/code&gt;）給 &lt;code&gt;base_url&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;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="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="c1"&gt;# 配置遠端 Ollama API
&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_API_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;http://192.168.0.37:11434/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# 遠端 Ollama 伺服器
# OLLAMA_API_URL = "http://127.0.0.1:11434/v1"  # 本機 Ollama 伺服器
&lt;/span&gt;&lt;span class="n"&gt;MODEL_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;gemma3:12b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 初始化 OpenAI 客戶端，指向 Ollama 的 API
&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;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&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;MODEL_NAME&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ollama</category>
    </item>
    <item>
      <title>用 Inkscape 批次幫 SVG 檔文字轉外框</title>
      <dc:creator>codemee</dc:creator>
      <pubDate>Wed, 30 Jul 2025 09:55:24 +0000</pubDate>
      <link>https://dev.to/codemee/yong-inkscape-pi-ci-bang-svg-dang-wen-zi-zhuan-wai-kuang-c3n</link>
      <guid>https://dev.to/codemee/yong-inkscape-pi-ci-bang-svg-dang-wen-zi-zhuan-wai-kuang-c3n</guid>
      <description>&lt;p&gt;我的工作會遇到需要把 SVG 檔內含的文字轉外框，這可以透過開放原始碼的 &lt;a href="https://inkscape.org/" rel="noopener noreferrer"&gt;Inkscape&lt;/a&gt; 處理，而且 Inkscape 還提供 &lt;a href="https://inkscape.org/doc/inkscape-man.html" rel="noopener noreferrer"&gt;CLI 介面&lt;/a&gt;，可以像是透過 Selenium 或是 Playwright 操控瀏覽器那樣自動化。於是我就請 &lt;a href="https://grok.com/share/bGVnYWN5_c6cd1273-43db-4c8d-bc54-c6b69b34e8b3" rel="noopener noreferrer"&gt;Grok&lt;/a&gt; 幫我設計腳本，讓我可以批次處理一堆 SVG 檔轉外框在存檔的功能，以下是透過指令把 SVG 轉外框的批次檔：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="c"&gt;REM batch_text_to_path.bat&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="vm"&gt;%%f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;(*&lt;/span&gt;.svg&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="kd"&gt;Processing&lt;/span&gt; &lt;span class="vm"&gt;%%f&lt;/span&gt;...
    &lt;span class="s2"&gt;"C:\Program Files\Inkscape\bin\inkscape.exe"&lt;/span&gt; &lt;span class="na"&gt;--actions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"select-all;org.inkscape.text.convert_to_path;export-filename:&lt;/span&gt;&lt;span class="vm"&gt;%%~nf&lt;/span&gt;&lt;span class="s2"&gt;_converted.svg;export-do"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="vm"&gt;%%f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;它會把目前資料夾下的所有 SVG 檔用 Inkscape 一一開啟，開啟後：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;選取所有物件&lt;/li&gt;
&lt;li&gt;文字轉外框&lt;/li&gt;
&lt;li&gt;以原檔名加上 "_converted" 字尾存檔&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;也有 bash 版：&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# batch_text_to_path.sh&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.svg&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Processing &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    inkscape &lt;span class="nt"&gt;--actions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"select-all;org.inkscape.text.convert_to_path;export-filename:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;%.svg&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_converted.svg;export-do"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;或是 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="c"&gt;# batch_text_to_path.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$inkscapePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\Inkscape\bin\inkscape.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 根據你的 Inkscape 安裝路徑調整&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$svgFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;svg&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$svgFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Processing &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$inkscapePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--actions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"select-all;org.inkscape.text.convert_to_path;export-filename:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BaseName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;_converted.svg;export-do"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Saved as &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BaseName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;_converted.svg"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果想知道有哪些動作可以透過 CLI 完成，可以執行 &lt;code&gt;--action-list&lt;/code&gt;：&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="n"&gt;c:\Program&lt;/span&gt;&lt;span class="se"&gt;` &lt;/span&gt;&lt;span class="nx"&gt;Files\Inkscape\bin\inkscape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--action-list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>inkscape</category>
      <category>svg</category>
    </item>
  </channel>
</rss>
