2025年3月4日 星期二

OpenAI API 學習筆記 : 如何查詢全部模型名稱

自從 OpenAI 在 2022 年 12 月推出 ChatGPT 後, 大語言模型如雨後春筍般出現, 即使是 OpenAI 也從 GPT3 開始快速演化到 GPT4, 要如何得知有哪些模型可用呢? 到 OpenAI 官網有各代版本的詳細說明, 參考 : 


但是如果想要有一張目前可用的模型名稱清單的話, 只要呼叫 API 即可取得. 為了隱藏 API key, 我將其命名為 OPENAI_API 並存放於目前工作目錄下的 .env 檔中, 然後安裝 dotenv 套件來從 .env 檔案中取得 API key, 作法參考 :


首先匯入 dotenv.load_dotenv() 函式與 os 模組 : 

>>> from dotenv import load_dotenv   
>>> import os   

呼叫 load_dotenv() 函式載入 .env 檔 : 

>>> load_dotenv()    
True 

傳回 True 表示載入成功, 呼叫 os.environ.get() 讀取檔案中的 API key : 

>>> api_key=os.environ.get('OPENAI_API')     

將此 API key 設給 openai 的 api_key : 

>>> openai.api_key=api_key   

呼叫 models 模組的 list() 函式會傳回一個 SyncPage[Model] 物件 :  

>>> models=openai.models.list()   
>>> type(models)   
<class 'openai.pagination.SyncPage[Model]'>

模型物件就記錄在其 data 屬性中 (為一個串列), 因此迭代此串列元素即可取得 Model 物件, 模型名稱則記錄在 Model 物件的 id 屬性中 : 

>>> for model in models.data:
    print(model.id)

gpt-4.5-preview
omni-moderation-2024-09-26
gpt-4.5-preview-2025-02-27
gpt-4o-mini-audio-preview-2024-12-17
dall-e-3
dall-e-2
gpt-4o-audio-preview-2024-10-01
gpt-4o-audio-preview
gpt-4o-mini-realtime-preview-2024-12-17
gpt-4o-2024-11-20
gpt-4o-mini-realtime-preview
o1-mini-2024-09-12
o1-preview-2024-09-12
o1-mini
o1-preview
gpt-4o-mini-audio-preview
whisper-1
gpt-4o-2024-05-13
gpt-4o-realtime-preview-2024-10-01
babbage-002
chatgpt-4o-latest
gpt-4-turbo-preview
tts-1-hd-1106
gpt-4o-audio-preview-2024-12-17
gpt-4
gpt-4-turbo
tts-1-hd
gpt-4o-mini-2024-07-18
gpt-4o-mini
text-embedding-3-large
tts-1
tts-1-1106
gpt-4o-2024-08-06
davinci-002
gpt-4o
gpt-3.5-turbo-1106
gpt-3.5-turbo-instruct
gpt-3.5-turbo-instruct-0914
gpt-3.5-turbo-0125
gpt-4-0125-preview
gpt-4o-realtime-preview-2024-12-17
gpt-3.5-turbo
gpt-4-turbo-2024-04-09
gpt-4o-realtime-preview
gpt-3.5-turbo-16k
text-embedding-3-small
gpt-4-1106-preview
text-embedding-ada-002
gpt-4-0613
omni-moderation-latest

看來最新的模型是 gpt-4.5-preview-2025-02-27. 越新的模型其價格通常越貴, 且會隨時調整, 最新價格參考官網 :


高科大還書 3 本 (Deep Learning 3 等)

由於有三本預約書到館, 今天下班後拿下面這三本最近較少看的書去交換 :
No.1 這本有許多反爬蟲素材與驗證碼破解技巧 (但目前還用不到); No.2 主要是從無到有打造一個像 PyTorch 那樣的框架 (雖然可深入理解框架的運作原理, 但卻是重複造輪子工程); No.3 內容含括資料科學, 機率統計, 以及機器學習, 是必須回借的好書 (副本也多). 

2025年3月3日 星期一

料理實驗 : 薑汁燒肉

前陣子在 YT 看到下面這個楊桃美食的薑汁燒肉食譜 :





這道菜不僅配料簡單, 還非常下飯好吃, 剛好冰箱有肉片, 就趁 228 連假試做, 結果大成功, 我跟爸都吃了兩碗飯. 

材料 : 
  • 豬肉片 1 盒
  • 洋蔥 1 顆
  • 薑 1 塊
  • 醬油 適量
  • 米酒 適量
  • 味晽 適量 (也可用雞精粉)
  • 白芝麻 適量
做法 : 
  1. 洋蔥切絲備用, 薑磨成薑末, 碗中倒入適量醬油, 味晽與米酒, 倒入薑末攪拌均勻, 取一半倒入肉片中醃一下去腥.
  2. 將洋蔥放入鍋中炒軟, 放入醃過的肉片並倒入另一半醬汁拌炒至肉片變白, 撒入白芝麻即可起鍋. 








嗯, 等下周小狐狸們回鄉下掃墓時再來做這道菜. 

2025 年第 9 周記事

本周因周五 228 放假只上四天班, 我週四晚上早早就回鄉下了, 順路去凹仔底接婷婷表妹, 因上周回歸職場的她要趕回去接管兩個住在鄉下家的雙胞胎兒子, 哎呀, 這就是我 20 年前似曾相識的生活主旋律啊! 

三天連假算是很充實, 有休閒也有苦力活, 流水帳紀錄如下 :
  1. 2/28 (五) : 早上整理來台祖墓園, 下午載爸去三聖換藥, 兩周前被機車腳架戳到腳踝留下三個小傷口, 循環較差癒合慢每天去換藥. 順路去小漢買六尺浪板 (800 元) 與矽膠黏著劑, 準備來修補車庫年前被我踩破的採光罩屋頂. 然後去燦坤刷卡買了 LG 17 公斤洗衣機 (加 1000 延長為全機 7 年保固共 23899 元), 預定 3/15 裝機. 傍晚整理來台祖墓園左側雜草堆. 
  2. 3/1 (六) : 老同學文壬來鄉下找我一起去爬旗尾山 (上一次是 2017 年), 中午登頂後下山在山腳下吃午飯再去糖廠吃冰, 度過優閒的一天. 傍晚整理來台祖墓園左側雜草堆. 
  3. 3/2 (日) : 在小舅協助下完成車庫洗衣機上方採光罩以及原客廳被蛀蟲蛀壞的紅木常椅修補.
唯一未完成任務是餐廳櫃子的清理, 上週已組好的收納櫃還是擺在客廳無法歸定位, 等下周再來處理. 

本周小灰家族折損兩隻貓咪, 讓我心情非常糟. 週四我打電話回家時詢問貓況, 爸說小灰第一胎兩隻大貓公之一週四早上躺在菜園廁所外的小徑上死了, 原因不明 (中毒?), 已埋在菜園的酪梨樹下. 我週四晚上回到家就先走到樹旁幫它念往生咒, 雖然第一胎跟我不親, 但每次我晚上回到鄉下, 大貓公總是會過來討貓糧. 第二天 2/28 晚上, 爸在曬穀場騎腳踏車, 小灰第二胎的三隻小喵有一隻跑到路對面, 在奔回途中被疾駛而過的車子撞死, 看著一動也不動的小喵讓我很難過, 第二胎因為小時候都關在車庫鐵櫃保護, 我常常抱牠們, 所以感情比較深, 但也只能唸咒迴向給小喵. 

除了貓咪外, 對面張家伙房的榮彩伯也在周二往生了, 伯母與母親常相往來, 母親過世後常送自種的青菜過來, 理應過去捻香, 但由於年前吾妹才剛安金於家族墓園, 老師說四個月內不要包白包, 所以便傳簡訊給榮彩伯女兒, 表達哀悼之意但不便前往捻香. 榮彩伯的獨子是我國中同學, 讀書時每天都先騎腳踏車來我家大門口等我一起上學, 品學兼優的他本是師大高材生準老師, 可惜英年早逝, 那件事對我的心理衝擊很大. 

學習部分終於把 Gradio 剩餘的 Chatbot 與 State 元件用法測試完, 也利用它們寫了一個聊天機器人. 其實二月份我大都在 Hahow 企業版上課, 因為我的會員只到今年九月底, 本周主要是重看了古君葳老師的 GitPage 架站課程 (很棒的一堂課), 另外是閱讀手上的兩本 LangChain 書籍 (還沒實測), 所以測試筆記也寫得很少, 但在進入 LangChain 之前還是把 OpenAI API 做一個較完整的測試比較好 (最好加上 Gemini 或 Grok). 

2025年3月1日 星期六

爬旗尾山

早上老同學文壬來鄉下找我, 相約去爬旗尾山, 2017 年那一年曾經走了四趟, 今天從第一入山口進, 第三入山口出, 半天行程邊走邊聊天還算輕鬆, 只是太陽甚大, 還好稜線有樹蔭曬不太到. 很久沒爬山了, 今日天氣又好, 沿路拍了幾張不錯的照片 :






正午時終於登上旗山祠, 稍事休息便下山, 在回糖廠路上看見一家日式便當店 "雲水山房", 便進去吃過午餐再回糖廠吃冰, 度過了愜意的一天 : 




離開糖廠後又去了黃蝶翠谷, 但只在門口看看而已, 下回再來走一趟森林步道.  

2025年2月28日 星期五

好站 : 如何設定 Windows 11 螢幕保護

最近 LG Gram 筆電 Win 11 更新後螢幕保護與電源設定好像被重設了, 太久沒用忘記在哪設了, 搜尋找到下面這篇寫得好詳盡 :


簡言之, 就是在桌面按滑鼠右鍵, 依照下列步驟進行設定 :

個人化 > 鎖定畫面 > 螢幕保護裝置

2025年2月27日 星期四

Python 學習筆記 : 使用 DuckDuckGo API 搜尋網路資料 (三)

在前面的測試中, 我們發現 DuckDuckGo 物件的 chat() 背後會串接 OpenAI 的語言模型 (預設使用 gpt-4o-mini 模型). 本篇要使用 Gradio 的 Chatbot 元件作為 UI 介面來實作 AI 聊天機器人. 

本系列之前的文章參考 :


 
9. 使用 Gradio 的 Chatbot 製作 DuckDuckGo 聊天機器人 : 

Gradio 的 Chatbot 元件會用聊天泡泡形式呈現對話過程, 是專為聊天機器人之類的應用而設計的 UI 元件, 作法參考下面這篇串接 OpenAI API 的聊天機器人 :


本測試使用 Chatbot 元件透過 DuckDuckGo 物件的 chat() 方法來與 gpt-3.5-turbo 或 gpt-4-flash 模型聊天, 與上面串接 OpenAI API 不同之處是 chat() 會自動記憶之前的聊天歷史, 因此不需把對話歷史作為 prompt 傳送給模型, 程式碼如下 :

# duckduckgo_chatbot_1.py
import gradio as gr
from duckduckgo_search import DDGS

def ask_duckduckgo(prompt):
    global chat_history
    chat_history.append({'role': 'user', 'content': prompt})
    bot_response=ddgs.chat(prompt)
    chat_history.append({'role': 'assistant', 'content': bot_response})
    return chat_history

ddgs=DDGS()
chat_history=[]
prompt=gr.Textbox(label='您的詢問: ')
chatbot=gr.Chatbot(type='messages',
                   height=400,
                   placeholder='我們的對話',
                   show_copy_button=True)
iface=gr.Interface(
    fn=ask_duckduckgo,
    inputs=prompt,  
    outputs=chatbot,
    title='DuckDuckGo 聊天機器人',
    flagging_mode='never',
    )
iface.launch()

此例設置了一個 Textbox 輸入元件 prompt 來輸入提示詞, 以及一個 Chatbot 元件來輸出對話歷史, 並以一個串列 chat_history 來記錄對話歷史. 當輸入提示詞, 按下 Submit 按鈕時會呼叫 fn 所指的處理函式 ask_duckduckgo(), 將 prompt 傳給 DDGS 物件的 chat() 方法即可取得回應, 結果如下 : 






可見即使沒有將之前的對話歷史連同 prompt 傳給 chat(), 它依然能記得之前說了甚麼. 

但是上面的聊天機器人使用全域變數 chat_history 來儲存對話紀錄並不安全, 較好的做法是使用 Blocks 元件自行排版以建立一個語境, 然後在裡面建立一個 State 物件, 它會自動儲存 Chatbot 元件內的對話歷史. 

修改後的程式如下 :

# duckduckgo_chatbot_2.py
import gradio as gr
from duckduckgo_search import DDGS

def ask_duckduckgo(prompt, history):
    history.append({'role': 'user', 'content': prompt})
    try:
        bot_response=ddgs.chat(prompt) 
        history.append({'role': 'assistant', 'content': bot_response})
    except Exception as e:
        history.append({'role': 'assistant', 'content': f'發生錯誤:{str(e)}'})
    return history, ""

ddgs=DDGS()
with gr.Blocks(title='DuckDuckGo 聊天機器人') as blocks:
    chatbot=gr.Chatbot(type='messages',
                       height=400,
                       placeholder='我們的對話',
                       show_copy_button=True)
    state=gr.State([])  # 建立狀態物件來記錄對話歷史
    with gr.Column():
        prompt=gr.Textbox(label="您的詢問:")
        send_btn=gr.Button("送出")    
    send_btn.click(ask_duckduckgo, inputs=[prompt, state], outputs=[chatbot, prompt])
    prompt.submit(ask_duckduckgo, inputs=[prompt, state], outputs=[chatbot, prompt])
blocks.launch()

此例做了如下變動 : 
  • 呼叫 chat() 的程式碼被放入 try except 結構內以避免出現例外時 app 崩潰.
  • ask_duckduckgo() 傳回值增加一個空字串元素用來清空提示詞輸入框以便能直接輸入下一個提示詞, 這對應到 click() 方法 outputs 參數的第二元素 prompt. 
  • 增加 prompt.submit() 以便能在提示詞輸入框按下 Enter 時效果與按下送出鈕一樣. 
結果如下 : 




上面的程式仍有不足之處, 例如無法清除聊天紀錄重新開始, 我們可以在版面中增加一個 "清除歷史" 的按鈕來達成, 程式碼修改如下 :

# duckduckgo_chatbot_3.py
import gradio as gr
from duckduckgo_search import DDGS

def clear_history():
    return [], []  # 同時清除 chatbot 和 state

def ask_duckduckgo(prompt, history):
    history.append({'role': 'user', 'content': prompt})
    try:
        bot_response=ddgs.chat(prompt) 
        history.append({'role': 'assistant', 'content': bot_response})
    except Exception as e:
        history.append({'role': 'assistant', 'content': f'發生錯誤:{str(e)}'})
    return history, ""

ddgs=DDGS()
with gr.Blocks(title='DuckDuckGo 聊天機器人') as blocks:
    chatbot=gr.Chatbot(type='messages',
                       height=400,
                       placeholder='我們的對話',
                       show_copy_button=True)
    state=gr.State([])
    with gr.Column():
        prompt=gr.Textbox(label="您的詢問:")
        send_btn=gr.Button("送出")
        clear_btn=gr.Button("清除對話")
    send_btn.click(ask_duckduckgo, inputs=[prompt, state], outputs=[chatbot, prompt])
    prompt.submit(ask_duckduckgo, inputs=[prompt, state], outputs=[chatbot, prompt])
    clear_btn.click(clear_history, inputs=None, outputs=[chatbot, state])
blocks.launch()

此例增加了一個 clear_btn 按鈕, 它的輸出有兩個 : chatbot 與 state 物件, 當按下此按鈕時會呼叫 clear_history() 函式, 它會傳回兩個空串列分別清除 chatbot 與 state 物件的內容, 這樣不僅是聊天泡泡內容全部被清除, 連內部儲存的對話歷史狀態也被清除, 結果如下 : 




但是當我按下 "清除歷史" 再次詢問我養的貓咪, 它居然還記得! 這是因為 DuckDuckGo 本身的記憶還在的緣故, 解決之道是要把 DDGS 物件重新起始, 也就是重新建立一個 DDGS 物件, 修改後的程式碼如下 :

# duckduckgo_chatbot_3.py
import gradio as gr
from duckduckgo_search import DDGS

def clear_history():
    global ddgs       # 取用全域變數
    ddgs=DDGS()   # 重新初始化
    return [], []  # 同時清除 chatbot 和 state

def ask_duckduckgo(prompt, history):
    history.append({'role': 'user', 'content': prompt})
    try:
        bot_response=ddgs.chat(prompt) 
        history.append({'role': 'assistant', 'content': bot_response})
    except Exception as e:
        history.append({'role': 'assistant', 'content': f'發生錯誤:{str(e)}'})
    return history, ""

ddgs=DDGS()
with gr.Blocks(title='DuckDuckGo 聊天機器人') as blocks:
    gr.Markdown("# DuckDuckGo 聊天機器人")
    chatbot=gr.Chatbot(type='messages',
                       height=400,
                       placeholder='我們的對話',
                       show_copy_button=True)
    state=gr.State([])
    with gr.Column():
        prompt=gr.Textbox(label="您的詢問:")
        send_btn=gr.Button("送出")
        clear_btn=gr.Button("清除對話")
    send_btn.click(ask_duckduckgo, inputs=[prompt, state], outputs=[chatbot, prompt])
    prompt.submit(ask_duckduckgo, inputs=[prompt, state], outputs=[chatbot, prompt])
    clear_btn.click(clear_history, inputs=None, outputs=[chatbot, state])
blocks.launch()

此例主要的變動是在 clear_history() 中利用 global 取用全域變數 ddgs, 然後重新初始化一個 DDGS 物件給它, 這樣就可以重設 DuckDuckGo API 內儲存之歷史對話紀錄了. 另外使用 gr.Markdown() 在 Web app 開頭添加標題. 

首先送出提示詞 '我有養兩隻貓, 名叫小咪與萬萬' 後按下清除歷史鈕, 詢問 '你還記得我養了幾隻貓嗎? 名字是甚麼?' 顯示結果如下 : 




這樣果然就順利清除歷史對話了. 

我把最後一個範例程式佈署在 HuggingFace Space 上 : 


PS : 為了在手機螢幕能看到送出鈕, 我將 Chatbot 元件高度改為 320px.

2025年2月26日 星期三

明儀買書 4 本 (LangChain 奇幻旅程等)

傍晚去楠梓工作室接菁菁回家, 吃過晚飯時間還早, 騎機車載菁菁憶起去明儀買書, 因為今年又收到文化幣, 說要給我買書, 今天有先打電話問明儀是否可用文化幣, 答覆是不僅可以, 他們獨立書店還可回饋點數, 1200 元回饋 600 元, 亦即總共可用 1800 元, 哇, 這比去年在高鐵金石堂買還好, 雖然也有優惠, 但記得那時要用定價來算, 明儀則是以打八折算. 

下班前就把 momo 購物車之前想買的四本書請明儀找好放櫃台, 晚上即可直接取書了 :
總價 2630 元打八折為 2104 元, 扣掉文化幣 1800 元自付 304 元. 

2025年2月25日 星期二

Python 學習筆記 : 簡單好用的 Web app 套件 gradio (九)

最近因為測試 DuckDuckGo 搜尋引擎的 API, 回頭找 Gradio 教學文件才發現它還有一個專門用在開發聊天機器人的好物 : Chatbot 類別, 今天就來測試看看吧! 

本系列文章索引參考 : 



十二. 使用 Chatbot 元件製作聊天機器人 :    

Chatbot 類別是 Gradio 專為對話式應用設計的 UI 元件, 它會以類似聊天泡泡的形式呈現使用者與聊天機器人之間的互動紀錄 (支援 Markdown 語法), 但它只是呈現聊天輸出, 仍需搭配 Textbox 與 Button 等輸入 UI 元件才能實現完整的聊天介面. 


1. 建立 Chatbot 物件 : 

呼叫 Chatbot() 建構式並傳入參數即可建立 Chatbot 物件 : 

chatbot=gr.Chatbot(type='messages', height=400, placeholder='我們的對話')

Chatbot() 建構式常用參數如下表 : 


Chatbot() 常用參數 說明
type value 的類型 : "tuples" (舊版串列對格式, 預設) 或 "messages" (新版)
value 初始對話歷史, 舊版格式為二維串列對, 新版為 role 與 content 鍵之字典串列 
height 聊天視窗高度, 可以是整數 (單位 px) 或字串 (如 "400px")
placeholder 對話歷史為 None 時顯示的文字
label 聊天視窗上方的標籤文字
bubble_full_width 聊天泡泡是否佔據整個寬度, 預設 True
avatar_images 使用者與機器人的頭像 (tuple), 格式 (user_avatar, bot_avatar)
render_markdown 是否將對話內容渲染為 Markdown 格式, 預設 True
show_copy_button 是否在聊天泡泡旁顯示「複製」按鈕, 預設 False
visible 元件是否可見, 預設 True
scale 在佈局中相對於其他元件的縮放比例, 類型為 int, 預設 0
min_width 元件的最小寬度 (單位 px), 類型為 int


注意, type 參數預設值為 "tuples", 表示聊天歷史 value 參數值的格式是二維串列對 : 

[[user_message, bot_response], ...]  

例如 : 

 [["嗨", "你好"], ["你是誰", "我是 AI 助理"], ...]

但新版 Gradio 已經改用 type="messages", 表示 value 參數要使用如下字典串列格式 : 

[{"role": "user", "content": "提示詞1"}, {"role": "assistant", "content": "AI 回覆1"}, ... ]

但為了向下支援, type 預設值仍然是 "tuples", 因此使用新版的 Chatbot 時 type 參數務必設定為 "messages", 例如 : 

[{"role": "user", "content": "嗨"}, {"role": "assistant", "content": "你好"}]

下面是一個鸚鵡聊天機器人的範例, 它會將收到的訊息直接回應給詢問者 : 


import gradio as gr

def handler(in1):
    global chat_history
    chat_history.append({'role': 'user', 'content': in1})
    bot_response=in1  # 鸚鵡回應
    chat_history.append({'role': 'assistant', 'content': bot_response})
    return chat_history

chat_history=[]  # 儲存對話歷史
in1=gr.Textbox(placeholder='輸入你的訊息 ...')
out1=gr.Chatbot(type='messages', height=400, placeholder='我們的對話')
iface=gr.Interface(
    fn=handler,
    inputs=in1,
    outputs=out1,
    title='鸚鵡聊天機器人',
    flagging_mode='never'  
    )
iface.launch()

此例使用一個串列 chat_history 來儲存對話歷史紀錄, 其元素格式為以 role 與 content 為鍵的字典序列 (新版 Gradio Chatbot 格式), 處理函式 handler 會將詢問者的輸入訊息直接放入 content 鍵中存入歷史對話串列中後傳回給輸出元件 Chatbot 物件, 結果如下 : 




此例只是用來說明 Chatbot 用法的偽聊天機器人, 它只會鸚鵡學語而已. 參考下面範例改成串接 OpenAI API 與大語言模型聊天, 但用的是 Textbox 元件 :


下面範例則是改用 Chatbot : 

import gradio as gr
from openai import OpenAI

def ask_gpt(api_key, prompt):
    global chat_history
    chat_history.append({'role': 'user', 'content': prompt})
    client=OpenAI(api_key=api_key)    
    chat_completion=client.chat.completions.create(
        messages=chat_history,
        model='gpt-3.5-turbo'
        )
    reply=chat_completion.choices[0].message.content
    chat_history.append({'role': 'assistant', 'content': reply})
    return chat_history

chat_history=[]  # 儲存對話歷史
api_key=gr.Textbox(label='輸入 OpenAI 金鑰', type='password') 
prompt=gr.Textbox(label='您的詢問: ')
chatbot=gr.Chatbot(type='messages', height=400, placeholder='我們的對話')
iface=gr.Interface(
    fn=ask_gpt,
    inputs=[api_key, prompt],  
    outputs=chatbot,
    title='OpenAI API 聊天機器人',
    flagging_mode='never',
    )
iface.launch()

此例放置了兩個 Textbox 輸入元件, 一個用來貼上 OpenAI API Key, 另一個用來輸入提示詞, 歷史對話紀錄則是輸出到 Chatbot 元件上, 在處理函式 ask_gpt() 中會先把提示詞以 role=user 角色存入 hat_history 串列中, 然後將此串列傳給 API 的 create() 的 messages 參數, 取得回應後將其以 role=assistant 存入 chat_history 串列中, 結果如下 :




可見使用 Chatbot 元件就可以顯示完整的聊天泡泡了. 


2. 利用 Blocks 與 State 元件管理聊天狀態 : 

上面的範例使用全域變數 chat_history  來管理對話歷史不安全, 且可能被 Gradio 重設, 比較理想的方式是使用 Blocks 元件來建立一個區塊語境, 這樣就可以在裡面使用 gr.State 元件來管理聊天狀態. 

將上面的範例修改為如下 :

# gradio_chatbot_test_3.py
import gradio as gr
from openai import OpenAI

def ask_gpt(api_key, prompt, history):
    history.append({'role': 'user', 'content': prompt})
    client=OpenAI(api_key=api_key)
    try:
        chat_completion=client.chat.completions.create(
            messages=history,
            model='gpt-3.5-turbo'
            )
        reply=chat_completion.choices[0].message.content
        history.append({'role': 'assistant', 'content': reply})        
    except Exception as e:
        history.append({'role': 'assistant', 'content': f'發生錯誤:{str(e)}'})
    return history, ""

with gr.Blocks(title="OpenAI 聊天機器人") as blocks:
    api_key=gr.Textbox(label='輸入 OpenAI 金鑰', type='password') 
    chatbot=gr.Chatbot(type='messages',
                       height=400,
                       placeholder='我們的對話',
                       show_copy_button=True)
    state=gr.State([])  # 建立狀態物件儲存對話歷史
    with gr.Column():
        prompt=gr.Textbox(label="您的詢問:")
        send_btn=gr.Button("送出")    
    send_btn.click(ask_gpt, inputs=[api_key, prompt, state], outputs=[chatbot, prompt])
    prompt.submit(ask_gpt, inputs=[api_key, prompt, state], outputs=[chatbot, prompt])
blocks.launch()

此例我們改用 Blocks 物件取代 Interface 物件來排版, 這樣就可以在其語境內用 State 物件來儲存對話歷史, 毋須使用不安全之全域變數. 

當按下按鈕時會呼叫回呼函式 ask_gpt(), 並傳入三個參數, 其中第三個就是儲存對話歷史的 State 物件, 傳入 ask_gpt() 後改名為 history, 新版 Chatbot 元件使用 type='message' 指定 history 格式為 OpenAI 的 List[Dict] 格式, 因此要用 role 與 content 為鍵將 prompt 加入 history 中查詢; 回應也是用相同方式加入 history 中. 由於 click() 的 outputs 參數指定了兩個輸出 (chatbot 與 prompt), 所以 ask_gpt() 需傳回一個 tuple, 第二個元素傳回空字串的目的是要自動清空提示詞欄位讓使用者能直接輸入下一個提示詞. 呼叫 prompt 的 submit() 函式則是用在當使用者在 prompt 輸入框按下 Enter 鍵時做出與按下送出按鈕一樣的效果. 結果如下 :




看來 GPT 是有記住對話紀錄 (但 gpt-3.5-turbo 為何要道歉?). 

2025年2月23日 星期日

2025 年第 8 周記事

時間飛逝, 2025 已經快噴掉兩個月了, 但世界卻已經有了天翻地覆的變化, 打了幾年的俄烏戰爭被剛上任的川普強力要求終止, 死了這麼多人卻換來個寂寞, 這世界已經不是我們所能理解的那個世界了, 黑的瞬間變白, 白的也立刻變黑, 真是太神奇了. 川普看似是在賣聯俄制中的膏藥, 但誰知道那會不會終究只是個愚蠢的幻想而已, 到頭來也是換來個寂寞.

上週在 momo 買的收納櫃周一就到貨了, 週六載回鄉下, 今天下午弄完晚餐備料後便著手來組裝, 組第一個前花了一分鐘時間看說明書, 覺得寫得太簡略了看不太懂, 直接拆封一個來摸索, 三兩下就組好了, 兩大一小收納櫃花了半小時才全部搞定 : 




另一個四層收納櫃因為要放在鞋櫃上, 所以不裝輪子. 接下來是要將餐廳清理一下, 把被蛀掉的木櫃清掃出來, 這兩個新收納櫃才能進駐, 等下周 228 x連假時間較多再處理. 

水某周一要去桃園搭機前往新加坡參加臨床試驗會議, 但早上卻還有病人回診, 為了順利趕上 10 點半前的高鐵, 我周一早上請了兩小時假在急診對面的小七打程式, 過了十點多終於結束, 火速前往高鐵, 還好及時搭到 10 點半班車. 晚上水某傳來面海的飯店照片, 哎呀, 我好像應該陪她去的, 就算待在旅館耍廢三天也不錯. 

市圖還書 8 本 (C++ 與日文)

最近從川普粗暴脫歐體認到一件事 : 人腦運作是 single-tasking 模式, 正如美軍無法應付兩面作戰一樣. 我一直想要抽空來學習 C++ 進階的部分, 但現實是時間永遠不夠, 光是 Python 就學不完了, 哪有空檔分給 C++ 呢? 況且根本也沒有因為效能而需要用到 C++ 的時候, 所以都還了吧! "Life is short, you need Python". 

日文也是一樣, 一直希望能恢復以前的日文功力, 但殘酷的現實是, 我每天只會接觸與使用英文而已, 學日文這種只能在單一國家使用的外語 CP 值真的超低 (頂多旅遊有用, 但現在有 AI), 所以也扔了吧!  "Life is short, you need English". 

C++ 書籍 : 


日文書籍 : 
不過以上八本確實都是好書. 

2025年2月22日 星期六

好站 : 資料科學家的工作日常

兩周前臨時決定加入維元老師的 LLM 極速上手體驗課 (5 堂學費 2500 元), 本周一的第二堂課臨時改在今天早上 10:30, 但 10:00 QRV 又要做 140K 保養, 所以今天我是在 NISSAN 的保養等待室上這堂直播課, 結束後才發現雖然從兩三年前公司內訓資料科學課程就追蹤老師臉書, 但卻一直未訂閱他的頻道, 立馬訂閱 : 


裡面有一些資料科學訪談影片還不錯. 

2025年2月21日 星期五

Python 學習筆記 : 簡單好用的 Web app 套件 gradio (八)

Gradio 原本去年底我就大致測試完畢, 但最近在測試 DuckDuckGo API 時發現它還有一個好用的 Block 元件, 可以用來自訂 web app 版面, 讓開發者可以更靈活地排列各種 UI 元件, 此外它也提供狀態管理與事件處理功能, 非常適合用來製作 AI 聊天機器人或 Dashboard (儀錶板) 等較複雜的應用程式介面. 

本系列文章索引參考 : 



十一. 用 Block 元件自訂 web app 版面 : 

在之前的 Gradio 測試中, 我們都使用 Interface 類別來建立 web app 的標準網頁介面, 只要用 inputs, outputs, 以及 fn 參數指定輸出入元件與 Submit 按鈕之處理函式即可簡單地建立應用程式介面. 但若要建立結構較複雜的 App, 則 Interface 所提供的簡單且固定的版面就不敷使用了, 這時就需要用到 Blocks 與 Row, Column, Tab, 或 Acordion 等布局元件來自行為介面排版. 

Blocks 類別是一個比 Interface 類別還低階的 API, 呼叫其建構式 Blocks() 會建立一個 Blocks 物件 : 

>>> import gradio as gr 
>>> blocks=gr.Blocks()   
>>> type(blocks)   
<class 'gradio.blocks.Blocks'>   

用 Blocks 布局元件時需要用到 with 語法形成一個語境 (context) 區塊, 然後在此語境內使用 Row, Column, Tab, 或 Acordion 等布局元件為其他 UI 元件進行排版 (也都使用 with 語法) : 

with gr.Blocks() as blocks:
    # 在此 context 內進行 UI 元件布局
    with gr.Row(): # 列布局 
        # 放置水平排列的 UI 元件 (由左向右)
    with gr.Column(): 
        # 放置垂直排列的 UI 元件  (由上而下)
    with gr.Tab():
        # 放置第一個頁籤內的 UI 元件  (由左向右)
    with gr.Tab():
        # 放置第二個頁籤內的 UI 元件  (由左向右)
    with gr.Acordion():
        # 放置手風琴內的 UI 元件  (由左向右)

使用 with 語法的好處是能確保 Blocks 物件在執行完 with 語境後能自動釋放資源, 在語境內部建立的 UI 元件都會被自動加入 Blocks 物件內, 使程式碼更整潔更有可讀性. 

最後在語境外呼叫 Blocks 物件的 launch() 方法即可發布 web app : 
   
blocks.launch()


1. 僅使用 Blocks 排版 : 

這種排版方式不使用 Row, Column, Tab, 或 Acordion 等布局元件, 直接將 UI 元件放在 Blocks 語境內, 這時 UI 元件會流水式依序由左向右, 由上而下排列, 例如下面將輸入文字轉成大寫的簡單範例中, 我們直接在 blocks 語境內建立了一個 TextBox 輸入與輸出元件, 以及一個 Button 元件 : 

>>> with blocks:
 in1=gr.Textbox(label="輸入文字")
 btn=gr.Button("送出")
 out1=gr.Textbox(label="輸出結果")
 btn.click(lambda in1: in1.upper(), inputs=in1, outputs=out1) 

 {'id': 0, 'targets': [(6, 'click')], 'inputs': [5], 'outputs': [7], 'backend_fn': True, 'js': None, 'queue': True, 'api_name': 'lambda', 'scroll_to_output': False, 'show_progress': 'full', 'batch': False, 'max_batch_size': 4, 'cancels': [], 'types': {'generator': False, 'cancel': False}, 'collects_event_data': False, 'trigger_after': None, 'trigger_only_on_success': False, 'trigger_mode': 'once', 'show_api': True, 'zerogpu': False, 'rendered_in': None, 'connection': 'sse', 'time_limit': None, 'stream_every': 0.5, 'like_user_message': False, 'event_specific_args': None}

其中 Button 物件的第一個參數是按鈕事件的處理函式, 並且用關鍵字 inputs 要傳入此函式的輸入元件, 以及用 outputs 指定接收函式傳回值的輸出元件. 

接著只要呼叫 Blocks 物件的 launch() 方法發布此 web app : 

>>> blocks.launch()    
* Running on local URL:  http://127.0.0.1:7860

To create a public link, set `share=True` in `launch()`. 

結果如下 :




按鈕事件處理函式也可以用 def 來定義, 然後於 click() 方法中以 fn 參數指定 :

import gradio as gr

def handler(text):
    return text.upper()

with gr.Blocks() as blocks:
     in1=gr.Textbox(label="輸入文字")
     btn=gr.Button("送出")
     out1=gr.Textbox(label="輸出結果")
     btn.click(fn=handler , inputs=in1, outputs=out1)
     
blocks.launch()

結果是一樣的. 


2. 使用 Row 列布局元件排版 : 

Row 類別用來將 UI 元件以水平方式 (由左向右) 做橫列排版, 呼叫其建構式 Row() 即可建立一個列布局物件, 與 Blocks 物件用法一樣, 也是使用 with gr.Row() 用法建立一個 Row 物件語境, 然後在其內佈放 UI 物件, 例如 : 

import gradio as gr

def handler(text):
    return text.upper()

with gr.Blocks() as blocks:
    with gr.Row():
        in1=gr.Textbox(label="輸入文字")
        btn=gr.Button("送出")
        out1=gr.Textbox(label="輸出結果")
    btn.click(fn=handler , inputs=in1, outputs=out1)
     
blocks.launch()

此處 in1, btn, 與 out1 三個 UI 元件佈放在 Row 物件的語境內, 它們會被水平排列於版面中, 結果如下 : 




可見三個 UI 元件以橫列方式水平排列. 


3. 使用 Column 行布局元件排版 : 

Column 類別用來將 UI 元件以垂直方式 (由上而下) 做垂直排版, 呼叫其建構式 Column() 即可建立一個行布局物件, 與 Blocks 物件用法一樣, 也是使用 with gr.Column() 用法建立一個 Column 物件語境, 然後在其內佈放 UI 物件, 例如 : 

# gradio_blocks_test_3.py
import gradio as gr

def handler(text):
    return text.upper()

with gr.Blocks() as blocks:
    with gr.Column():
        in1=gr.Textbox(label="輸入文字")
        btn=gr.Button("送出")
        out1=gr.Textbox(label="輸出結果")
    btn.click(fn=handler , inputs=in1, outputs=out1)
     
blocks.launch()

此處 in1, btn, 與 out1 三個 UI 元件佈放在 Column 物件的語境內, 它們會被垂直排列於版面中, 結果如下 : 




可見這與上面直接在 Blocks 物件內排版的效果是一樣的. 


4. 使用 Tab 頁籤布局元件排版 :  

Tab 類別用來將 UI 元件以頁籤的方式排版, 呼叫其建構式 Tab() 並傳入頁籤標題即可建立一個頁籤布局物件, 與 Blocks 物件用法一樣, 也是使用 with gr.Tab() 用法建立一個 Tab 物件語境, 然後在其內佈放 UI 物件, 通常需要建立一個以上的 Tab 語境來各自佈放頁籤內之 UI 元件, 例如 : 

# gradio_blocks_test_4.py
import gradio as gr

def upper(text):
    return text.upper()

def lower(text):
    return text.lower()

with gr.Blocks() as blocks:
    with gr.Tab('轉成大寫'):
        in1=gr.Textbox(label="輸入文字")
        btn1=gr.Button("送出")
        out1=gr.Textbox(label="輸出結果")
    with gr.Tab('轉成小寫'):
        in2=gr.Textbox(label="輸入文字")
        btn2=gr.Button("送出")
        out2=gr.Textbox(label="輸出結果")       
    btn1.click(fn=upper, inputs=in1, outputs=out1)
    btn2.click(fn=lower, inputs=in2, outputs=out2)
     
blocks.launch()

此處建立了兩個頁籤物件, 並在各自語境下佈放 UI 元件, 分別達成轉大寫與轉小寫的 web app 功能, 結果如下 : 





按頁籤標題即可切換至轉大寫與轉小寫之頁籤. 


5. 使用 Accordion 手風琴布局元件排版 :  

Accordion 類別用來將 UI 元件以手風琴版面方式排版, 呼叫其建構式 Accordion() 並傳入標題 (第一參數) 即可建立一個手風琴版面物件, 與 Blocks 物件用法一樣, 也是使用 with gr.Accordion() 用法建立一個 Accordion 物件語境, 然後在其內佈放 UI 物件. 手風琴預設是開啟的, 但可以傳入 open=False 將其關閉, 例如 : 

# gradio_blocks_test_5.py
import gradio as gr

def upper(text):
    return text.upper()

def lower(text):
    return text.lower()

with gr.Blocks() as blocks:
    with gr.Accordion('轉成大寫'):
        in1=gr.Textbox(label="輸入文字")
        btn1=gr.Button("送出")
        out1=gr.Textbox(label="輸出結果")
    with gr.Accordion('轉成小寫', open=False):
        in2=gr.Textbox(label="輸入文字")
        btn2=gr.Button("送出")
        out2=gr.Textbox(label="輸出結果")       
    btn1.click(fn=upper, inputs=in1, outputs=out1)
    btn2.click(fn=lower, inputs=in2, outputs=out2)
     
blocks.launch()

此例建立了兩個手風琴物件, 分別佈放轉大寫與轉小寫的 UI 元件, 其中轉大寫的預設是開啟, 轉小寫的社為關閉, 初始化時結果如下 : 





按手風琴右邊的三角形按鈕可以收合或開啟版面 : 




這是關閉轉大寫, 開啟轉小寫版面的效果. 


6. 搭配 State 物件進行狀態管理 :  




2025年2月18日 星期二

如何在 Colab 平台設定時區

Colab 是一個非常方便的 Python 執行平台, 除了內建的標準函式庫外, 也已經預先安裝了許多常用的第三方套件 (主要是資料科學與機器學習套件), 幾乎毋須配置環境即可馬上執行 Python 應用程式. 如果套件或模組無法直接 import 進來, 表示 Colab 沒有預先安裝, 可用 !pip install 指令自行安裝. 

本系列之前的文章參考 :

如何在 Colab 中隱藏與取用密鑰

本篇要來測試如何將 Colab 上預設的 UTC 日期時間改為本地時間.


1. 利用 os 與 time 模組設定時區 : 

Colab 的虛擬機器時區預設是 UTC 時區, 例如下列指令執行後印出的是 UTC 時間 :

from datetime import datetime   
print(datetime.now())   




現在台灣時間為下午近五點, 但顯示的卻是早上近九點, 差了 8 小時 (台灣是 UTC+8). 

可以匯入 os 與 time 模組後利用 os.environ['TZ'] 設定時區為 'ASIA/Taipei', 然後呼叫 time.tzset() 函式設定時區, 這樣 datetime.now() 就會印出台灣時間了 : 

import os   
import time   

os.environ['TZ']='Asia/Taipei'  
time.tzset()   





2. 利用第三方套件 pytz 設定時區 :   

先匯入 pytz 模組 (目前 Colab 已經預先安裝了) :

import pytz   

然後呼叫其 timezone() 函式並傳入 'ASIA/Taipei' 將時區設定為台灣時區後, 呼叫 datetime 物件的 astimezone() 方法並傳入 TimeZone 物件即可 : 

tz=pytz.timezone('Asia/Taipei')  
utc_now=datetime.now(pytz.utc)  
print(utc_now)    
print(utc_now.astimezone(tz))    



2025年2月17日 星期一

好站 : 範例檔案資料庫網站 samplelib.com

今天為了測試 Gradio 的 Video 元件搜尋線上 mp4 檔案, 找到下面這個好用的範例文件資料庫網站 samplelib.com :





此網站提供視訊 (MP4, WEBM), 音訊 (MP3, WAV), 圖片 (JPEG, PNG, SVG, GIF), Excel 檔, 廣告橫幅, 以及排版用的 Lorem ipsum 文字檔等範例, 可供直接以 URL 方式線上或下載免費無限制使用. 

以視訊檔為例, 點擊首頁上方 Video 選單中的 mp4 選項會列出所有視訊檔範例列表, 點擊右方按鈕即可下載檔案 :




如果要線上使用, 在視訊檔上按滑鼠右鍵, 點選 "複製影片位址" 即可取得 URL :




例如第一個影片網址為 : 

https://samplelib.com/lib/preview/mp4/sample-5s.mp4

將此 URL 傳給 Gradio 的 Video 元件就可以在 web app 中播放此影片了. 

2025年2月16日 星期日

2025 年第 7 周記事

本周重要事項為周四 (2/13) 辦理阿蘭安金滿月祭祀, 我上週即已籌備妥當, 周四請早上半天假, 週三下班後經自由路買了一對花束回鄉下, 先至退伍軍人取預定之發粄與紅龜粄, 週四早上仍下小雨, 八點時剛好暫歇, 便與爸出發前往墓園祭拜. 到斟第三回酒時又下起雨來, 趕緊燒金放鞭炮, 然後點香收祭品, 酒杯裡的米酒都摻了一些雨水了. 至此阿蘭的身後事總算圓滿了. 

這一周三天兩頭都在下雨, 天氣又冷又濕, 晾乾的衣服總覺得不夠乾爽還得用乾衣機烘個幾分鐘. 週六總算迎來太陽 (中午還超級熱), 早上載菁菁回楠梓工作室後回鄉下. 周末主要工作是烹飪, 把冰箱祭祀用的雞肉做麻油雞, 豬肉做成紅燒肉, 帶回高雄當下周午餐便當的主菜. 還有過年拜拜用的麵條, 因為剛好全聯買一送一, 結果現在廚房堆了四大包關廟麵, 今日中午就煮台式炒麵來消耗一些. 

晒穀場邊的波蘿蜜已結實累累, 今日中午得空便拿了梯子進行疏果, 每個分支僅留一顆較大的果實, 其餘全部切除, 避免營養分散每顆都長不大. 往年我都無暇疏果任其生長, 節實太密導致有些小果被夾在中間很容易爛掉. 去年都沒有送農會拍賣, 爸的波羅蜜外快收入有限, 今年要好好來看顧, 我可以周一載去農會交運後再去高雄上班, 因為現在彈性上班往後延到 09:30 時間綽綽有餘. 



二哥碩二 (下) 註冊

今天幫二哥線上註冊碩二下的學雜費, 共 32716 元 : 




大部分是宿舍費啦. 二哥說他論文包含兩顆晶片, 目前已完成一顆, 還有一顆晶片新學期要下, 加上論文撰寫可能要碩三上才畢業. 他室友資工所的台北人, 下學期只要準備論文沒有課了, 所以新學期退宿了, 二哥還要住宿舍一年哩. 但校內住宿補貼不知道下個學年度還有沒有, 真的要感謝藍白幫納稅人看緊荷包, 真的啦. 

2025年2月15日 星期六

momo 購買茶几與收納櫃

除夕大掃除時發現老家客廳的茶几被蛀蟲侵蝕, 四支腳已經有一支被蛀空, 只要稍用力壓就可能垮掉, 去鎮上家具行看了一下, 一個要價 3000 元實在太貴了, 只好等過完年再來尋覓. 今天早上去燦坤看洗衣機回來, 想到茶几也要換, 便上 momo 找到下面這款 : 





另外餐廳的四格木櫃也被白蟻啃蝕, 最近要將其全部清除, 換成塑膠收納櫃, 靠樓梯那邊的受樓梯限制擺這個三層櫃 : 






靠電視這側的買四層櫃, 買兩組, 另一組放鞋櫃上面 : 





總計需付 2969 元, 用掉 momo 幣 189 元, 實付 2771 元 :





有登記送 10% momo 幣活動, 但今天已滿額, 明晚 9 點再登記 : 



2025年2月14日 星期五

Python 學習筆記 : 使用 DuckDuckGo API 搜尋網路資料 (二)

在前一篇測試中, 我們對 DuckDuckGo API 用法已有基本了解, 只要安裝第三方套件 duckduckgo-search 即可在應用程式中透過 DuckDuckGo API 搜尋文字, 圖片, 影片, 新聞等內容, 甚至可與 GPT 語言模型聊天, 參考本系列前一篇文章 :


本篇則要使用 Gradio 作為網頁應用程式介面來顯示從 DuckDuckGo 搜尋到的圖片. 


8. 使用 Gradio 製作圖片搜尋介面 : 

Gradio 是 Hugging Face 旗下的一款輕量級開放原始碼 Python web app 套件, 開發者毋須具備任何網頁前端技術 (例如 HTML, CSS, Javascript), 只需少許 Python 程式碼即可快速建構一個網頁應用程式介面, 用法參考 :


Gradio 官網提供 Playground 讓開發者線上撰寫 Web app, 網址如下 : 


在 Gradio Playgorund 上編寫的應用程式可以一鍵部署到 Hugging Face 的免費 Web app 空間 Hugging Face Space (也可付費取得更多資源), 參考 : 


Hugging Face Space 已安裝一些常用的 Python 第三方套件, 例如 Numpy, Pandas, Matplotlib 等等, 通常在 Gradio Playgorund 上可以順利執行的 Web app 發布到 Hugging Face Space 後應該均可順利運行, 如果出現找不到套件或模組的錯誤, 就要到 Hugging Face Space 上手動新增一個專案, 並在 requirement.txt 檔案中指定要安裝之第三方套件, 參考 :


不過壞消息是, 由於透過 DuckDuckGo API 搜尋資料需要 requests 或 urllib 等爬蟲套件, 但 Gradio Playground 與 Hugging Face Space 可能基於資安原因均無法執行這些爬蟲套件, 因此以下測試均在本機執行. 

Gradio 用來展示圖片的元件為 Gallery, 用法參考 : 


全部 Gradio 測試紀錄參考索引 :


測試程式碼如下 :

# gradio-duckduckgo-images-gallery-1.py
import gradio as gr
from duckduckgo_search import DDGS

def handler(query, max_results):
    results=ddgs.images(query, max_results=int(max_results))  # 需用 int() 轉成整數
    return [result['thumbnail'] for result in results]
    
ddgs=DDGS()
in1=gr.Textbox(label='輸入關鍵字', value='可愛的貓')
in2=gr.Textbox(label='設定圖片數量', value=8)
out1=gr.Gallery(label='DuckDuckGo 圖片搜尋結果', columns=4, object_fit='scale-down')
iface=gr.Interface(
    fn=handler,
    inputs=[in1, in2],
    outputs=out1,
    title='DuckDuckGo 圖片搜尋測試',
    flagging_mode='never',
    )
iface.launch()

此例設置了兩個 TextBox 輸入元件, in1 負責接收搜尋關鍵字, in2 負責接收欲取得之圖片數量 (即取前幾個搜尋結果), 這兩個參數會依序傳入處理函式 fn=handler, 分別對應到 query 與 max_results, 然後傳給 DDGS 物件的 images() 方法進行圖片搜尋, 然後從 DuckDuckGo API 回應的字典串列中, 取出 thumbnail 鍵所攜帶的縮圖 URL 組成串列傳回給 Gallery 元件顯示. 注意, 由於 Textbox 元件輸出字串, 但 max_results 參數要求為一個整數, 故需先用 int() 轉換. 

結果如下 : 




因為 Gallery 元件指定 columns=4, 所以圖片會以 4 欄呈現, 點擊這些小圖就會彈出一個較大視窗來顯示這些縮圖 (不是原始圖檔). 如果將 handler() 中的傳回值改成擷取原本圖檔的 URL 鍵 image 亦可 : 

return [result['image'] for result in results]

但因為 Gradio 的 Gallery 能顯示的圖檔尺寸有限, 太大的圖檔會無法呈現, 例如 : 




也可以用 Number 或 Slider 元件來設定圖片數量, 它們都是傳回數值, 因此毋須使用 int() 來轉換 in2 元件的傳出值, 例如 : 

# gradio-duckduckgo-images-gallery-2.py
import gradio as gr
from duckduckgo_search import DDGS

def handler(query, max_results):
    results=ddgs.images(query, max_results=max_results)
    return [result['thumbnail'] for result in results]
    
ddgs=DDGS()
in1=gr.Textbox(label='輸入關鍵字', value='可愛的貓')
in2=gr.Number(label='設定圖片數量', minimum=1, value=8, maximum=100)
out1=gr.Gallery(label='DuckDuckGo 圖片搜尋結果', columns=4, object_fit='scale-down')
iface=gr.Interface(
    fn=handler,
    inputs=[in1, in2],
    outputs=out1,
    title='DuckDuckGo 圖片搜尋測試',
    flagging_mode='never',
    )
iface.launch()

結果如下 : 





下面是使用 Slider 的範例 :

# gradio-duckduckgo-images-gallery-3.py
import gradio as gr
from duckduckgo_search import DDGS

def handler(query, max_results):
    results=ddgs.images(query, max_results=max_results)
    return [result['thumbnail'] for result in results]
    
ddgs=DDGS()
in1=gr.Textbox(label='輸入關鍵字', value='可愛的貓')
in2=gr.Slider(label='設定圖片數量', minimum=1, value=8, maximum=100, step=1)
out1=gr.Gallery(label='DuckDuckGo 圖片搜尋結果', columns=4, object_fit='scale-down')
iface=gr.Interface(
    fn=handler,
    inputs=[in1, in2],
    outputs=out1,
    title='DuckDuckGo 圖片搜尋測試',
    flagging_mode='never',
    )
iface.launch()

由於 Slider 滑動步階預設為 0.1, 因此這裡要將 step 設為 1 (上面的 Number 元件沒有 step 參數, 因為它固定為 1, 結果如下 :