2026年5月30日 星期六

Ollama 學習筆記 : REST API 用法 (二)

在前一篇測試中, 我們在 PS 視窗裡使用 curl.exe 程式向 Ollama 建立的 REST API 伺服器提出請求, 本篇則是要改用 Python 程式來存取 REST API 端點 (使用 requests 套件). 

本系列全部測試文章索引參考 :


茲將常用之 Ollama REST API 端點鈔錄如下 :


 POST 操作端點  說明
 /api/generate  文字生成 (用於單次提示詞的輸入與基本文字接龍)。
 /api/chat  多輪對話 (用於需要記錄上下文, 用 user/assistant/system 區分角色)。
 /api/embeddings  使用 Embedding 模型將文字轉成可用於 RAG 語意搜尋的嵌入向量。
 /api/pull  從 Ollama 官方模型庫下載指定的 LLM 模型到本機。
 /api/push  將模型推入 Ollama 官方模型庫 (需登入 & 輸入金鑰)。
 /api/show  顯示模型的詳細資訊。
 /api/create  透過傳入 Modelfile 的內容從本機檔案建立或自訂一個全新的模型。
 /api/copy  在本地將一個現有的模型複製並重新命名為另一個名稱。

 GET 操作端點  說明
 /api/tags  列出本地所有模型。
 /api/ps  查看目前有哪些模型正載入在記憶體 (DRAM/VRAM)。
 /api/version  取得 Ollama 的版本資訊。

 DELETE 操作端點  說明
 /api/delete  刪除本地指定之模型 (使用 name 鍵指定)。


2. 使用 requests  提出請求 :

requests 套件提供 get(), post(), 與 delete() 等相對於 GET, POST, 與 DELETE 方法的函式, 首先來測試 GET 方法, 例如呼叫 /a[i/tags 端點來取得函式清單. 由於 get() 無法指定模型, 因此需先手動載入模型, 開啟一個 CMD 或 PS 視窗, 輸入下列指令載入執行 gemma4:e4b 模型 : 

PS C:\Users\USER> ollama run gemma4:e4b  

然後進入 Python 執行環境匯入 requests 套件, 呼叫 requests.get() 函式向 /api/tags 端點提出 GET 請求, requests 套件會將 Ollama 回應的 HTTP 封包打包成一個 Response 物件傳回 : 

>>> import requests  
>>> url='http://localhost:11434/api/tags'  
>>> reply=requests.get(url)  
>>> reply    
<Response [200]>
>>> type(reply) 
<class 'requests.models.Response'>  

可呼叫 Response 物件的 json() 方法轉成字典, 為了整齊顯示字典結構, 可用 pprint.print() 來輸出 : 

>>> from pprint import pprint   
>>> result=reply.json()    
>>> pprint(result)   
{'models': [{'details': {'families': ['gemma4'],
                         'family': 'gemma4',
                         'format': 'gguf',
                         'parameter_size': '8.0B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'c6eb396dbd5992bbe3f5cdb947e8bbc0ee413d7c17e2beaae69f5d569cf982eb',
             'model': 'gemma4:e4b',
             'modified_at': '2026-05-28T22:36:46.3502486+08:00',
             'name': 'gemma4:e4b',
             'size': 9608350718},
            {'details': {'families': ['mllama'],
                         'family': 'mllama',
                         'format': 'gguf',
                         'parameter_size': '10.7B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': '6f2f9757ae97e8a3f8ea33d6adb2b11d93d9a35bef277cd2c0b1b5af8e8d0b1e',
             'model': 'llama3.2-vision:11b',
             'modified_at': '2026-05-25T11:30:36.0322613+08:00',
             'name': 'llama3.2-vision:11b',
             'size': 7816589186},
            {'details': {'families': ['phi3'],
                         'family': 'phi3',
                         'format': 'gguf',
                         'parameter_size': '14.7B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'ac896e5b8b34a1f4efa7b14d7520725140d5512484457fab45d2a4ea14c69dba',
             'model': 'phi4:14b',
             'modified_at': '2026-05-24T17:23:24.1907029+08:00',
             'name': 'phi4:14b',
             'size': 9053116391},
            {'details': {'families': ['deepseek2'],
                         'family': 'deepseek2',
                         'format': 'gguf',
                         'parameter_size': '15.7B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'a6f5c73087ad25fc8666929492449eb0dc694326e4ca5b2313fef75b66645583',
             'model': 'dagbs/deepseek-coder-v2-lite-instruct:q4_k_m',
             'modified_at': '2026-05-24T00:17:21.5900783+08:00',
             'name': 'dagbs/deepseek-coder-v2-lite-instruct:q4_k_m',
             'size': 10364417401},
            {'details': {'families': ['deepseek2'],
                         'family': 'deepseek2',
                         'format': 'gguf',
                         'parameter_size': '15.7B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': '6171206208d0529a47806ebcf8ed37a88fe322859e269396dd16fdd98a56a102',
             'model': 'mannix/deepseek-coder-v2-lite-instruct:q4_k_m',
             'modified_at': '2026-05-23T21:15:32.5340567+08:00',
             'name': 'mannix/deepseek-coder-v2-lite-instruct:q4_k_m',
             'size': 10364432240},
            {'details': {'families': ['qwen2'],
                         'family': 'qwen2',
                         'format': 'gguf',
                         'parameter_size': '14.8B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'c333b7232bdb521236694ffbb5f5a6b11cc45d98e9142c73123b670fca400b09',
             'model': 'deepseek-r1:14b',
             'modified_at': '2026-05-23T16:43:35.5527992+08:00',
             'name': 'deepseek-r1:14b',
             'size': 8988112209},
            {'details': {'families': ['qwen3'],
                         'family': 'qwen3',
                         'format': 'gguf',
                         'parameter_size': '14.8B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'bdbd181c33f2ed1b31c972991882db3cf4d192569092138a7d29e973cd9debe8',
             'model': 'qwen3:14b',
             'modified_at': '2026-05-22T00:30:36.3395998+08:00',
             'name': 'qwen3:14b',
             'size': 9276198565},
            {'details': {'families': ['gemma4'],
                         'family': 'gemma4',
                         'format': 'gguf',
                         'parameter_size': '8.0B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'c6eb396dbd5992bbe3f5cdb947e8bbc0ee413d7c17e2beaae69f5d569cf982eb',
             'model': 'gemma4:latest',
             'modified_at': '2026-05-20T11:45:08.2471048+08:00',
             'name': 'gemma4:latest',
             'size': 9608350718}]}

呼叫 /api/ps 端點會傳回目前載入記憶體之模型資訊 (相當於下 ollama ps 指令) : 

>>> url='http://localhost:11434/api/ps'    
>>> reply=requests.get(url)   
>>> result=reply.json()   
>>> pprint(result)  
{'models': [{'context_length': 4096,
             'details': {'families': ['gemma4'],
                         'family': 'gemma4',
                         'format': 'gguf',
                         'parameter_size': '8.0B',
                         'parent_model': '',
                         'quantization_level': 'Q4_K_M'},
             'digest': 'c6eb396dbd5992bbe3f5cdb947e8bbc0ee413d7c17e2beaae69f5d569cf982eb',
             'expires_at': '2026-05-30T08:51:38.2129288+08:00',
             'model': 'gemma4:e4b',
             'name': 'gemma4:e4b',
             'size': 10579079040,
             'size_vram': 10579079040}]}  


可見呼叫 /api/tags 端點會列出目前 Ollama 已下載的全部模型. 

但若呼叫距離前一次存取模型超過逾時時間 (預設 5 分鐘), Ollama 會自動將模型會從記憶體中清除, 這時呼叫 /api/ps 端點會得到 models 鍵為空串列的回應 : 

>>> pprint(result)  
{'models': []}

這時只要用 POST 方法呼叫 /api/generate 或 /api/chat, Ollama 便會觸發 Ollama 自動將指定之模型載入記憶體 (毋須手動在命令列用 ollama run 指令載入). 

注意, 呼叫 get() 無法指定模型, 必須呼叫 post() 才能指定模型, 因為呼叫 post() 時可傳入一個 json 參數, 用字典來打包要傳給 Ollama 端點之酬載資訊 (payload), 其中的 model 鍵便是用來指定要載入之模型, 例如下面是呼叫文字接龍端點 /api/generate 的酬載資訊 :

payload={
    "model": "gemma4:e4b",
    "prompt": "請簡介量子糾纏",
    "stream": False
    }

例如 :

>>> url='http://localhost:11434/api/generate'   
>>> payload={
    "model": "gemma4:e4b",
    "prompt": "你是誰",
    "stream": False
    }
>>> reply=requests.post(url, json=payload)   
>>> result=reply.json()  
>>> pprint(result)  
{'context': [2,
             105,
             9731,
             107,
... (略) ...
             237169,
             240975,
             239967,
             236918,
             238463,
             237536],
 'created_at': '2026-05-30T01:28:46.9601674Z',
 'done': True,
 'done_reason': 'stop',
 'eval_count': 443,
 'eval_duration': 5984802600,
 'load_duration': 229697200,
 'model': 'gemma4:e4b',
 'prompt_eval_count': 18,
 'prompt_eval_duration': 235963400,
 'response': '我叫 **Gemma 4**。\n'
             '\n'
             '我是一個大型語言模型(Large Language Model, LLM),由 Google DeepMind 開發。\n'
             '\n'
             '我的設計目的是用來理解和生成人類語言,我可以協助您回答問題、撰寫不同類型的文本、總結資訊,或進行創意性的對話。\n'
             '\n'
             '您有什麼問題需要我幫忙的呢?',
 'total_duration': 6698511600}

可見對於單一問答 (接龍) 呼叫, Response 物件中包含一長串的 context 訊息, 而模型實際的回應則是放在 resposne 鍵裡 :

>>> print(result['response'])   
我叫 **Gemma 4**。

我是一個大型語言模型(Large Language Model, LLM),由 Google DeepMind 開發。

我的設計目的是用來理解和生成人類語言,我可以協助您回答問題、撰寫不同類型的文本、總結資訊,或進行創意性的對話。

您有什麼問題需要我幫忙的呢?

如果是呼叫對話端點 /api/chat, 則可在 messages 鍵中放入包含 role 與 content 鍵之對話字典, role 可以是 system/user/assistant, 格式與 OpenAI API 類似 : 

payload={
    "model": "gemma4:e4b",
    "messages": [
        {"role": "system", "content": "請一律用台灣用語與繁體中文回答"},
        {"role": "user", "content": "請用 100 個字簡介量子糾纏"}
        ],
    "stream": False
    }

例如 :

>>> url='http://localhost:11434/api/chat'    
>>> payload={
    "model": "gemma4:e4b",
    "messages": [
        {"role": "system", "content": "請一律用台灣用語與繁體中文回答"},
        {"role": "user", "content": "請用 100 個字簡介量子糾纏"}
        ],
    "stream": False
    }
>>> reply=requests.post(url, json=payload)  
>>> result=reply.json()   
>>> pprint(result)   
{'created_at': '2026-05-30T01:39:36.5316751Z',
 'done': True,
 'done_reason': 'stop',
 'eval_count': 595,
 'eval_duration': 8100674200,
 'load_duration': 4374553000,
 'message': {'content': '量子糾纏是一種奇特的量子現象,描述兩個或多個粒子之間存在的緊密關聯。無論這些粒子相距多遠,牠們的量子狀態都會形成一個不可分割的整體。\n'
                        '\n'
                        '當我們測量其中一個粒子的某個性質(例如自旋)時,另一個粒子的狀態會瞬間、同步地確定,彷彿有無形的超距聯繫一般。愛因斯坦曾戲稱為「幽靈般的超距作用」。\n'
                        '\n'
                        '這種現象是量子計算和量子密碼學的基礎技術,是未來尖端科技不可或缺的核心概念。它挑戰了我們對物理世界空間和資訊傳輸的傳統認知。',
             'role': 'assistant',
             'thinking': "Here's a thinking process to ensure the response "
                         'meets the criteria (Taiwanese Mandarin, Traditional '
                         'Chinese, approximately 100 characters, and '
                         'scientifically accurate description of quantum '
                         'entanglement):\n'
                         '\n'
                         '1.  **Analyze the Request:**\n'
                         '    *   Topic: Quantum Entanglement (量子糾纏).\n'
                         '    *   Length: Approximately 100 characters.\n'
                         '    *   Style: Use Taiwanese terminology and '
                         'Traditional Chinese (台灣用語與繁體中文).\n'
                         '\n'
                         '2.  **Identify Key Concepts (Quantum '
                         'Entanglement):**\n'
                         '    *   Definition: A unique connection between two '
                         'or more quantum particles.\n'
                         '    *   Feature: Their fates are linked, regardless '
                         'of the distance separating them.\n'
                         '    *   Measurement Effect: Measuring a property '
                         '(like spin) of one instantly tells you the property '
                         'of the other.\n'
                         '    *   Paradox/Implication: Einstein called it '
                         '"spooky action at a distance" (幽靈般的超距作用).\n'
                         '    *   Application: Crucial for quantum computing '
                         'and quantum cryptography.\n'
                         '\n'
                         '3.  **Drafting the Content (Focusing on the '
                         '"Taiwanese Tone" and Flow):**\n'
                         '    *   *Goal:* Make it accessible yet precise.\n'
                         '    *   *Initial Draft Check (Mental Outline):* '
                         'Start with the definition, explain the link, mention '
                         'the distance independence, and end with the '
                         'application/impact.\n'
                         '\n'
                         '4.  **Refining and Polishing (Character Count and '
                         'Tone):**\n'
                         '    *   *Self-Correction/Refinement:* Ensure the '
                         'language feels natural for a Taiwanese audience '
                         '(using common Taiwanese scientific phrasing, but '
                         'remaining formal).\n'
                         '    *   *(Structuring the 100 characters):*\n'
                         '        *   Introduction (What it is).\n'
                         '        *   Mechanism (The spooky link).\n'
                         '        *   Conclusion (Why it matters).\n'
                         '\n'
                         '5.  **Final Polish (Review against constraints):**\n'
                         '    *   Taiwanese/Traditional Chinese: Yes.\n'
                         '    *   Length: Close to 100 characters (The target '
                         'is an *approximation*).\n'
                         '    *   Accuracy: Yes, captures the core idea.\n'
                         '\n'
                         '6.  **Final Output Generation.** (This leads to the '
                         'provided good answer.)'},
 'model': 'gemma4:e4b',
 'prompt_eval_count': 40,
 'prompt_eval_duration': 35168300,
 'total_duration': 12994831300}

模型的實際回應是放在 message 鍵的 content 鍵裡 : 

>>> print(result['message']['content'])   
量子糾纏是一種奇特的量子現象,描述兩個或多個粒子之間存在的緊密關聯。無論這些粒子相距多遠,牠們的量子狀態都會形成一個不可分割的整體。

當我們測量其中一個粒子的某個性質(例如自旋)時,另一個粒子的狀態會瞬間、同步地確定,彷彿有無形的超距聯繫一般。愛因斯坦曾戲稱為「幽靈般的超距作用」。

這種現象是量子計算和量子密碼學的基礎技術,是未來尖端科技不可或缺的核心概念。它挑戰了我們對物理世界空間和資訊傳輸的傳統認知。

或者連續呼叫字典的 get() 方法亦可 : 

>>> print(result.get('message').get('content'))   
量子糾纏是一種奇特的量子現象,描述兩個或多個粒子之間存在的緊密關聯。無論這些粒子相距多遠,牠們的量子狀態都會形成一個不可分割的整體。

當我們測量其中一個粒子的某個性質(例如自旋)時,另一個粒子的狀態會瞬間、同步地確定,彷彿有無形的超距聯繫一般。愛因斯坦曾戲稱為「幽靈般的超距作用」。

這種現象是量子計算和量子密碼學的基礎技術,是未來尖端科技不可或缺的核心概念。它挑戰了我們對物理世界空間和資訊傳輸的傳統認知。


3. 核心參數 (外層) 與進階參數 (內層) :  

Ollama REST API 對話端點 /api/chat 與文字接龍端點 /api/generate 的參數結構分為外層的核心參數與內層的進階參數 (options) 兩種, Ollama 原生端點規定最外層只能放控制 API 行為的核心參數, 其餘微調參數必須全部打包塞進一個叫 "options" 的子字典裡; OpenAI 則是把所有控制 AI 腦袋的參數 (如隨機性與記憶力等) 全部平鋪在最外層. 

核心參數說明如下表 :


 核心參數名稱  說明
 model  必要。指定要執行的模型名稱(字串),例如 "gemma4:e4b"。
 messages  必要。對話紀錄串列,內含多個指定 role (system/user/assistant) 與 content 鍵之字典。
 stream  選用。是否以串流回應 (預設 True)。
 format  選用。指定回傳格式 (目前僅支援 "json" : 強迫模型輸出結構化的 JSON 字串, 預設 null)。
 keep_alive  選用。設定模型在留在記憶體中的時間 (預設為 "5m",設為 0 表示立刻釋放)。
 options  選用。進階模型微調參數字典 (例如 Temperature 或 num_ctx 等)。


內層的 options 的微調參數 (鍵) 說明如下表 :


 內層參數名稱  說明
 temperature  隨機性 : 值越高回答越有創意但易離題; 值越低回答越精準與固定 (預設 0.8)
 num_ctx  含輸入與輸出之上下文視窗大小 (預設為 2048 個 token)。
 num_predict  單次回答的最大 Token 數 (預設 -1 表示無限制)
 top_p  核心採樣機率, 限制模型只能從累積機率前 P% 的高機率詞彙中挑選字 (預設 0.9)。
 top_k  最高機率詞彙數。限制模型選字時只考慮機率最高的前 K 個詞 (預設 40)。
 repeat_penalty  重複字詞懲罰係數。避開已經講過的詞來防止重覆同一句話 (預設 1.1)。
 seed  隨機數種子 (整數)。強迫模型吐出一模一樣的答案 (預設 -1, 完全隨機)。


format 參數預設為 null, 模型會以自由文本 (Free Text) 模式回應. 如果 Python 程式需要串接資料庫, 或是進行網頁 RAG 數據結構化時, 可以利用 "format": "json" 參數設定, 要求模型以符合 JSON 格式的字串回應 (可直接轉成 Python 字典). 注意, 啟用 JSON 輸出必須同時在 prompt 或 messages 提示詞中同時明確告訴模型要輸出 JSON, 否則可能會讓此設定破功, 例如 : 

payload={
    "model": "gemma4:e4b",
    "messages": [
        {"role": "system", "content": "請一律用台灣用語與繁體中文回答"},
        {"role": "user", "content": "請列出全球人均 GDP 前十大國家, 包含 Country 與 GDP 兩欄位, 且以 JSON 格式回覆"}
        ],
    "format": "json",
    "stream": False
    }

>>> url='http://localhost:11434/api/chat'  
>>> payload={
    "model": "gemma4:e4b",
    "messages": [
        {"role": "system", "content": "請一律用台灣用語與繁體中文回答"},
        {"role": "user", "content": "請列出全球人均 GDP 前十大國家, 包含 Country 與 GDP 兩欄位, 且以 JSON 格式回覆"}
        ],
    "format": "json",
    "stream": False
    }
>>> reply=requests.post(url, json=payload)   
>>> result=reply.json()   
>>> type(result)   
<class 'dict'>   
>>> pprint(result)   
{'created_at': '2026-05-30T07:48:28.8572311Z',
 'done': True,
 'done_reason': 'stop',
 'eval_count': 512,
 'eval_duration': 6949736800,
 'load_duration': 4340405700,
 'message': {'content': '{\n'
                        '  "note": "由於「人均 '
                        'GDP」是高度依賴數據來源、計算時間點(例如是預估值還是實際值)以及使用哪個國際組織(如 IMF, '
                        '世界銀行)數據而有巨大變動的數據。此外,名單的具體數字和排名也可能因年度資料更新而改變。此處提供的數據為一個範例的參考格式,您應使用最新的權威資料庫(如世界銀行或 '
                        'IMF)來獲取最精準且最新的全球排名。由於我無法即時存取即時的國際金融數據,且無法確定您需要的「哪個年份」的數據,我會提供一個結構化的範例,並用一個說明性的年份數據作為範例。",\n'
                        '  "top_10_countries": [\n'
                        '    {\n'
                        '      "Country": "路易斯利亞 (Luxembourg)",\n'
                        '      "GDP (PPP, USD)": 120000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "新加坡 (Singapore)",\n'
                        '      "GDP (PPP, USD)": 105000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "挪威 (Norway)",\n'
                        '      "GDP (PPP, USD)": 95000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "瑞士 (Switzerland)",\n'
                        '      "GDP (PPP, USD)": 90000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "阿聯酋 (United Arab Emirates)",\n'
                        '      "GDP (PPP, USD)": 85000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "葡萄牙 (Portugal)",\n'
                        '      "GDP (PPP, USD)": 78000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "冰島 (Iceland)",\n'
                        '      "GDP (PPP, USD)": 75000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "愛爾蘭 (Ireland)",\n'
                        '      "GDP (PPP, USD)": 72000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "韓國 (South Korea)",\n'
                        '      "GDP (PPP, USD)": 65000\n'
                        '    },\n'
                        '    {\n'
                        '      "Country": "美國 (United States)",\n'
                        '      "GDP (PPP, USD)": 60000\n'
                        '    }\n'
                        '  ]\n'
                        '}',
             'role': 'assistant',
             'thinking': "Here's a thinking process to construct the desired "
                         'JSON response:\n'
                         '\n'
                         '1.  **Understand the Goal:** The user wants a list '
                         'of the top 10 countries by *per capita GDP* (人均 '
                         'GDP).\n'
                         '2.  **Understand the Format:** The output *must* be '
                         'in JSON format, with two specific fields: `Country` '
                         'and `GDP`.\n'
                         '3.  **Identify Constraints/Caveats (Crucial '
                         'Step):**\n'
                         '    *   The request requires real-time, global '
                         'economic data (top 10 per capita GDP).\n'
                         '    *   My knowledge cutoff is January 2025, and I '
                         'do not have access to a live, definitive, '
                         'up-to-the-minute economic database (like World Bank '
                         'or IMF).\n'
                         '    *   Therefore, the data I provide must be '
                         '*representative* of recent trends but might not be '
                         'the absolute current ranking. I must use '
                         'placeholder/estimated values and acknowledge the '
                         'nature of the data.\n'
                         '4.  **Determine the Data Structure (JSON):** The '
                         'best structure will be an array of objects.\n'
                         '    ```json\n'
                         '    [\n'
                         '      {"Country": "...", "GDP": "..." },\n'
                         '      {"Country": "...", "GDP": "..." },\n'
                         '      // ... up to 10\n'
                         '    ]\n'
                         '    ```\n'
                         '5.  **Gather Representative Data (Top 10 Per Capita '
                         'GDP):** *Self-Correction/Knowledge Retrieval:* High '
                         'per capita GDP countries usually include Luxembourg, '
                         'Singapore, Qatar, Ireland, Norway, etc. I will use '
                         'estimated/representative figures for the JSON '
                         'structure. (Note: I must ensure the GDP figures are '
                         'plausible approximations, usually given in USD.)\n'
                         '6.  **Final JSON Construction (Iterative '
                         'Drafting):** (Populate the structure with the '
                         'data.)\n'
                         '7.  **Review and Refine (Adherence to '
                         'Tone/Format):**\n'
                         '    *   *Language:* Use Taiwanese Mandarin (台灣用語, '
                         '繁體中文).\n'
                         '    *   *Tone:* Formal, professional.\n'
                         '    *   *Format:* Strict JSON.\n'
                         '    *   *Disclaimer:* It is essential to include a '
                         'disclaimer that economic figures change constantly '
                         'and the data provided is based on the knowledge '
                         'cutoff date.\n'
                         '\n'
                         '8.  **Final Output Generation.** (This leads to the '
                         'provided JSON and explanatory text.)'},
 'model': 'gemma4:e4b',
 'prompt_eval_count': 61,
 'prompt_eval_duration': 27626100,
 'total_duration': 18852258300}

可見模型成功傳回含有 JSON 格式的資料, 但它擅自將 GDP 欄位名稱改為 GDP (PPP, USD), 如果要避免此情形, 提示詞需要加以限制, 例如改為 :

請列出全球人均 GDP 前十大國家, 包含 Country 與 GDP 兩欄位, 且以 JSON 格式回覆. 注意, 欄位名稱請忠實使用 Country 與 GDP, 不可自行更改

上面的回應結果顯示, GDP 資料放在 result["messages"]["content"], 其值是包含 JSON 回應的字串, 先取出此回應字串 :

>>> json_string=result['message']['content']   

然後匯入內建的 json 模組, 呼叫其 loads() 函式將此 JSON 字串轉成 Python 字典 :

>>> import json   
>>> clean_data=json.loads(json_string)     
>>> clean_data  
{'note': '由於「人均 GDP」是高度依賴數據來源、計算時間點(例如是預估值還是實際值)以及使用哪個國際組織(如 IMF, 世界銀行)數據而有巨大變動的數據。此外,名單的具體數字和排名也可能因年度資料更新而改變。此處提供的數據為一個範例的參考格式,您應使用最新的權威資料庫(如世界銀行或 IMF)來獲取最精準且最新的全球排名。由於我無法即時存取即時的國際金融數據,且無法確定您需要的「哪個年份」的數據,我會提供一個結構化的範例,並用一個說明性的年份數據作為範例。', 'top_10_countries': [{'Country': '路易斯利亞 (Luxembourg)', 'GDP (PPP, USD)': 120000}, {'Country': '新加坡 (Singapore)', 'GDP (PPP, USD)': 105000}, {'Country': '挪威 (Norway)', 'GDP (PPP, USD)': 95000}, {'Country': '瑞士 (Switzerland)', 'GDP (PPP, USD)': 90000}, {'Country': '阿聯酋 (United Arab Emirates)', 'GDP (PPP, USD)': 85000}, {'Country': '葡萄牙 (Portugal)', 'GDP (PPP, USD)': 78000}, {'Country': '冰島 (Iceland)', 'GDP (PPP, USD)': 75000}, {'Country': '愛爾蘭 (Ireland)', 'GDP (PPP, USD)': 72000}, {'Country': '韓國 (South Korea)', 'GDP (PPP, USD)': 65000}, {'Country': '美國 (United States)', 'GDP (PPP, USD)': 60000}]}

人均 GDP 前十大國家資料放在 top_10_countries 鍵裡 : 

>>> countries_list=clean_data['top_10_countries']   
>>> countries_list   
[{'Country': '路易斯利亞 (Luxembourg)', 'GDP (PPP, USD)': 120000}, {'Country': '新加坡 (Singapore)', 'GDP (PPP, USD)': 105000}, {'Country': '挪威 (Norway)', 'GDP (PPP, USD)': 95000}, {'Country': '瑞士 (Switzerland)', 'GDP (PPP, USD)': 90000}, {'Country': '阿聯酋 (United Arab Emirates)', 'GDP (PPP, USD)': 85000}, {'Country': '葡萄牙 (Portugal)', 'GDP (PPP, USD)': 78000}, {'Country': '冰島 (Iceland)', 'GDP (PPP, USD)': 75000}, {'Country': '愛爾蘭 (Ireland)', 'GDP (PPP, USD)': 72000}, {'Country': '韓國 (South Korea)', 'GDP (PPP, USD)': 65000}, {'Country': '美國 (United States)', 'GDP (PPP, USD)': 60000}]

可以用 Pandas 將其轉成容易處理的 DataFrame :

>>> import pandas as pd   
>>> df=pd.DataFrame(countries_list)   
>>> df   
                      Country  GDP (PPP, USD)
0          路易斯利亞 (Luxembourg)          120000
1             新加坡 (Singapore)          105000
2                 挪威 (Norway)           95000
3            瑞士 (Switzerland)           90000
4  阿聯酋 (United Arab Emirates)           85000
5              葡萄牙 (Portugal)           78000
6                冰島 (Iceland)           75000
7               愛爾蘭 (Ireland)           72000
8            韓國 (South Korea)           65000
9          美國 (United States)           60000

注意, 由於未聯網搜尋, 資料的正確性可能受限. 

對於文字接龍的單次生成請求呼叫 /api/generate 端點時也可以用 json 參數限制輸出 JSON 格式資料, 下面我們改用 /api/generate 端點, 並加強提示詞關於欄位的限制重新測試 :

>>> url='http://localhost:11434/api/generate'  
>>> payload={ 
    "model": "gemma4:e4b",
    "prompt": "請列出全球人均 GDP 前十大國家, 包含 Country 與 GDP 兩欄位, 且以 JSON 格式回覆. 注意, 欄位名稱請忠實使用 Country 與 GDP, 不可自行更改",
    "format": "json",
    "stream": False
    }
>>> reply=requests.post(url, json=payload)  
>>> result=reply.json()   
>>> pprint(result)   
{'context': [2,
             105,
             9731,
             107,
             98,
             107,
             106,
             107,
             105,
             2364,
             107,
... (略) ...
             236783,
             107,
             138,
             236842,
             107,
             236783],
 'created_at': '2026-05-30T08:30:01.0055623Z',
 'done': True,
 'done_reason': 'stop',
 'eval_count': 329,
 'eval_duration': 4434360500,
 'load_duration': 4382299000,
 'model': 'gemma4:e4b',
 'prompt_eval_count': 64,
 'prompt_eval_duration': 40775600,
 'response': '{\n'
             '  "global_per_capita_gdp_top_10": [\n'
             '    {\n'
             '      "Country": "Singapore",\n'
             '      "GDP": "約 80,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Luxembourg",\n'
             '      "GDP": "約 140,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Qatar",\n'
             '      "GDP": "約 85,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Ireland",\n'
             '      "GDP": "約 90,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Norway",\n'
             '      "GDP": "約 80,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Switzerland",\n'
             '      "GDP": "約 95,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "United States",\n'
             '      "GDP": "約 75,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Australia",\n'
             '      "GDP": "約 65,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Iceland",\n'
             '      "GDP": "約 60,000 USD"\n'
             '    },\n'
             '    {\n'
             '      "Country": "Denmark",\n'
             '      "GDP": "約 55,000 USD"\n'
             '    }\n'
             '  ]\n'
             '}',
 'total_duration': 9343526600}

可見這回有遵照約束, 使用指定的 Country 與 GDP 做欄位名稱了. 其次, 與 /api/chat 不同的是, /api/generate 將回應放在字典的 response 鍵裡, 取出後用 json.loads() 將其轉成 Python 字典 :

>>> json_string=result['response']  
>>> clean_data=json.loads(json_string)  
>>> clean_data   
{'global_per_capita_gdp_top_10': [{'Country': 'Singapore', 'GDP': '約 80,000 USD'}, {'Country': 'Luxembourg', 'GDP': '約 140,000 USD'}, {'Country': 'Qatar', 'GDP': '約 85,000 USD'}, {'Country': 'Ireland', 'GDP': '約 90,000 USD'}, {'Country': 'Norway', 'GDP': '約 80,000 USD'}, {'Country': 'Switzerland', 'GDP': '約 95,000 USD'}, {'Country': 'United States', 'GDP': '約 75,000 USD'}, {'Country': 'Australia', 'GDP': '約 65,000 USD'}, {'Country': 'Iceland', 'GDP': '約 60,000 USD'}, {'Country': 'Denmark', 'GDP': '約 55,000 USD'}]}

人均 GDP 前十大國家資料放在 global_per_capita_gdp_top_10 鍵裡 : 

>>> countries_list=clean_data['global_per_capita_gdp_top_10']   
>>> countries_list  
[{'Country': 'Singapore', 'GDP': '約 80,000 USD'}, {'Country': 'Luxembourg', 'GDP': '約 140,000 USD'}, {'Country': 'Qatar', 'GDP': '約 85,000 USD'}, {'Country': 'Ireland', 'GDP': '約 90,000 USD'}, {'Country': 'Norway', 'GDP': '約 80,000 USD'}, {'Country': 'Switzerland', 'GDP': '約 95,000 USD'}, {'Country': 'United States', 'GDP': '約 75,000 USD'}, {'Country': 'Australia', 'GDP': '約 65,000 USD'}, {'Country': 'Iceland', 'GDP': '約 60,000 USD'}, {'Country': 'Denmark', 'GDP': '約 55,000 USD'}]

轉成 DataFrame :

>>> df=pd.DataFrame(countries_list)  
>>> df 
         Country            GDP
0      Singapore   約 80,000 USD
1     Luxembourg  約 140,000 USD
2          Qatar   約 85,000 USD
3        Ireland   約 90,000 USD
4         Norway   約 80,000 USD
5    Switzerland   約 95,000 USD
6  United States   約 75,000 USD
7      Australia   約 65,000 USD
8        Iceland   約 60,000 USD
9        Denmark   約 55,000 USD

怎麼排名跟上面呼叫 /api/chat 的不同啊. 

沒有留言 :