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, 結果如下 : 



2025年2月12日 星期三

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

最近在讀旗標的 "LangChain 開發手冊" 這本書時看到 DuckDuckGo 搜尋引擎的訊息 (LangChain 工具集有內建 DuckDuckGoSearchAPIWrapper 類別), 以前在安裝不知哪款瀏覽器時似乎預設搜尋引擎是 DuckDuckGo, 但是在 Google 主宰搜尋市場時代, 誰還會去用甚麼鴨子搜尋引擎呢? 但今天深入了解後才發現它的好, 優點摘要如下 :
  • 使用方便 : 提供免費且不需要金鑰的 API, 可整合到應用程式中
  • 保護隱私 : DuckDuckGo 瀏覽器不追蹤使用者, 不收集個人資料, 不紀錄瀏覽歷史 
  • 立場中立 : 不過濾搜尋結果 (谷歌根據使用者資料提供個人化搜尋結果) 
  • 廣告簡單 : 僅依據搜尋關鍵字提供直接相關之簡單廣告 (谷歌提供個人化廣告)
  • 輕量快速 : 網站無過多的廣告與追蹤腳本程式碼, 執行效能高
於是立馬來測試一下 DuckDuckGo API 的用法, 其 API 網址如下 :


可以直接用瀏覽器連線此網站手動輸入關鍵字進行搜尋 : 




也可以透過第三方套件於自己的應用程式中呼叫其 API 取得搜尋結果, 用法與 Google 搜尋的 API 類似, 參考 :



1. 安裝 duckduckgo-search 套件 : 

如果要在應用程式中使用 DuckDuckGo API 取得搜尋結果, 要先用 pip install 安裝 duckduckgo-search 套件 :

pip install duckduckgo-search   

D:\python\test>pip install duckduckgo-search   
Collecting duckduckgo-search
  Downloading duckduckgo_search-7.3.2-py3-none-any.whl.metadata (17 kB)
Collecting click>=8.1.8 (from duckduckgo-search)
  Downloading click-8.1.8-py3-none-any.whl.metadata (2.3 kB)
Collecting primp>=0.11.0 (from duckduckgo-search)
  Downloading primp-0.12.0-cp38-abi3-win_amd64.whl.metadata (13 kB)
Collecting lxml>=5.3.0 (from duckduckgo-search)
  Downloading lxml-5.3.1-cp310-cp310-win_amd64.whl.metadata (3.8 kB)
Requirement already satisfied: colorama in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from click>=8.1.8->duckduckgo-search) (0.4.6)
Downloading duckduckgo_search-7.3.2-py3-none-any.whl (19 kB)
Downloading click-8.1.8-py3-none-any.whl (98 kB)
Downloading lxml-5.3.1-cp310-cp310-win_amd64.whl (3.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.8/3.8 MB 4.1 MB/s eta 0:00:00
Downloading primp-0.12.0-cp38-abi3-win_amd64.whl (3.1 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 MB 7.0 MB/s eta 0:00:00
Installing collected packages: primp, lxml, click, duckduckgo-search
  Attempting uninstall: lxml
    Found existing installation: lxml 4.9.3
    Uninstalling lxml-4.9.3:
      Successfully uninstalled lxml-4.9.3
  Attempting uninstall: click
    Found existing installation: click 8.1.7
    Uninstalling click-8.1.7:
      Successfully uninstalled click-8.1.7
Successfully installed click-8.1.8 duckduckgo-search-7.3.2 lxml-5.3.1 primp-0.12.0

安裝後的套件名稱是 duckduckgo_search, 先匯入整個套件 :

>>> import duckduckgo_search    
>>> duckduckgo_search.__version__   
'7.3.2'

用 dir() 檢視套件內容 :

>>> dir(duckduckgo_search)   
['DDGS', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'duckduckgo_search', 'exceptions', 'logging', 'utils', 'version'] 

這些成員中的核心是 DDGS 類別, 其他都是子模組, 除 exceptions 開發者可用來處理例外, 其餘均為內部使用. 


2. 建立 DDGS 物件 : 

使用 DuckDuckGo API 搜尋必須先建立 DDGS 物件, 首先匯入 DDGS 類別, 然後呼叫其建構式 DDGS() 來建立物件 : 

>>> from duckduckgo_search import DDGS   
>>> ddgs=DDGS()     
>>> type(ddgs)   
<class 'duckduckgo_search.duckduckgo_search.DDGS'>  

用 dir() 檢視 DDGS 物件內容 : 

>>> dir(ddgs)   
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_chat_messages', '_chat_models', '_chat_tokens_count', '_chat_vqd', '_get_url', '_get_vqd', '_impersonates', '_impersonates_os', '_sleep', '_text_html', '_text_lite', 'chat', 'client', 'headers', 'images', 'news', 'parser', 'proxy', 'sleep_timestamp', 'text', 'videos']

其中 client, headers, proxy, parser, sllep_timestamp 五個為屬性, 大部分為 DDGS 內部使用或很少用到, 其餘為物件方法, 也是 DDGS 主要的功能所在, 提供文字, 圖片, 視訊, 新聞等搜尋功能, 以及聊天機器人, 用法說明摘要如下表 : 


DDGS 物件方法 說明
chat(query, model='gpt-3.5-turbo') AI 聊天機器人 (可指定 gpt-4o-mini 模型).
text(query, max_results=10, region='us', safesearch='moderate', timelimit=None) 一般文字搜尋, 回傳標題, 網址與摘要.
images(query, max_results=10, safesearch='moderate') 圖片搜尋, 回傳圖片 URL 及縮圖.
videos(query, max_results=10, region='us', safesearch='moderate') 影片搜尋, 回傳影片標題與連結.
news(query, max_results=10, region='us', safesearch='moderate') 新聞搜尋, 回傳新聞標題, 網址與摘要.


3. 文字搜尋 :  

呼叫 DDGS 物件的 text() 方法並傳入關鍵字 (也可指定搜尋區域 region 與傳回筆數 max_results 等參數) 進行搜尋會傳回一個包含標題, 網址, 摘要等搜尋結果的字典串列, 例如 : 

>>> query='Langchain'  
>>> results=ddgs.text(query)   
>>> type(results)   
<class 'list'> 
>>> len(results)   
10

可見傳回值是一個串列, 如果沒有指定 max_results 參數預設是最多傳回 10 筆搜尋結果. 檢視串列元素為字典, 包含 title (網頁標題), href (網址), 與 body (內容) 三個鍵 : 

>>> type(results[0])  
<class 'dict'>  
>>> results[0]   
{'title': 'LangChain', 'href': 'https://www.langchain.com/', 'body': '"Working with LangChain and LangSmith on the Elastic AI Assistant had a significant positive impact on the overall pace and quality of the development and shipping experience. We couldn\'t have achieved the product experience delivered to our customers without LangChain, and we couldn\'t have done it at the same pace without LangSmith."'}
>>> results[0].keys()   
dict_keys(['title', 'href', 'body'])

傳入 max_results 參數可以指定最多傳回幾筆搜尋結果, 例如 :

>>> results=ddgs.text(query, max_results=5)   
>>> len(results)      
5

下面利用迴圈輸出這 5 筆搜尋結果的標題 (title) 與超連結 (href) :

>>> i=1   
>>> for result in results:   
    print(f'{i}. {result["title"]}\n{result["href"]}')   
    i += 1   
    
1. LangChain
https://www.langchain.com/
2. Introduction | ️ LangChain
https://python.langchain.com/docs/introduction/
3. GitHub - langchain-ai/langchain: Build context-aware reasoning ...
https://github.com/langchain-ai/langchain
4. LangChain - Wikipedia
https://en.wikipedia.org/wiki/LangChain
5. LangChain
https://www.langchain.com/langchain

也可以傳入 region 參數限制搜尋結果網頁的地區與語言, 例如台灣繁體中文的 region 代號為 'zh-tw' : 

>>> results=ddgs.text(query, region='zh-tw', max_results=5)   
>>> i=1    
>>> for result in results:   
    print(f'{i}. {result["title"]}\n{result["href"]}')   
    i += 1   

1. LangChain
https://www.langchain.com/
2. LangChain是什麼?AI開發者必須了解的LLM開源框架 - ALPHA Camp
https://tw.alphacamp.co/blog/langchain-intro
3. LangChain 框架介绍 | ️ Langchain
https://docs.langchain.com.cn/docs/introduction/
4. 快速入門 | ️ LangChain 框架
https://langchain-python.dev.org.tw/docs/get_started/quickstart/
5. 一文搞懂LangChain是什么(非常详细),零基础入门到精通,看这一篇就够了-CSDN博客
https://blog.csdn.net/weixin_49895216/article/details/142848013

不過看起來似乎不分簡體繁體, 只要是中文都包含在內. 


4. 圖片搜尋 :  

呼叫 DDGS 物件的 images() 方法並傳入關鍵字 (也可指定搜尋區域 region 與傳回筆數 max_results 等參數) 會傳回一個包含網頁標題與網址, 圖片與其縮圖網址, 圖片尺寸等搜尋結果的字典串列, 例如 : 

>>> query='可愛的貓'  
>>> results=ddgs.images(query, max_results=5)  
>>> type(results)  
<class 'list'>  

檢視第一個元素內容為紀錄網頁網址, 圖片網址, 與圖片尺寸等訊息的字典 : 

>>> results[0]   
{'title': '真想養貓!6種適合新手認養的可愛貓咪 | 布偶貓 | 網紅貓 | 大紀元', 'image': 'https://i.epochtimes.com/assets/uploads/2020/10/shutterstock_1665879682.jpg', 'thumbnail': 'https://tse2.mm.bing.net/th?id=OIP.siVhfvYFrZi77bBl4fAJBgHaE7&pid=Api', 'url': 'https://www.epochtimes.com/b5/20/10/13/n12472751.htm', 'height': 3016, 'width': 4525, 'source': 'Bing'}
>>> results[0].keys()  
dict_keys(['title', 'image', 'thumbnail', 'url', 'height', 'width', 'source'])

其中 title 為網頁標題, images 為圖片網址, url 為包含此圖片之網頁的網址, height 與 width 分別為圖片之高度與寬度 (px), source 為網頁來源, thumbnail 為縮圖網址. 按圖片網址即可看到此圖片 : 





用迴圈列出這五筆圖片 : 

>>> i=1    
>>> for result in results:
    print(f'{i}. {result["title"]}:\n{result["image"]}')
    i += 1   
    
1. 真想養貓!6種適合新手認養的可愛貓咪 | 布偶貓 | 網紅貓 | 大紀元:
https://i.epochtimes.com/assets/uploads/2020/10/shutterstock_1665879682.jpg
2. 【貓桌布】可愛喵合集!13款治癒系喵星人桌布,快讓你的桌面被喵星人佔領~ | CatCity 貓奴日常:
https://image.presslogic.com/cats.presslogic.com/wp-content/uploads/2021/03/c3671215.jpeg
3. 【手機壁紙】第二五期 可愛貓咪壁紙 - 每日頭條:
https://i2.kknews.cc/SIG=bbhro/61690001p648srn7rnqp.jpg
4. 可爱猫|快乐版 - 全部作品 - 素材集市:
http://img.sucaijishi.com/uploadfile/2023/0210/20230210100626916.png?imageMogr2/format/jpg/blur/1x0/quality/60
5. 可爱的猫 向量, 可愛的, 画, 卡通貓向量圖案素材免費下載,PNG,EPS和AI素材下載 - Pngtree:
https://png.pngtree.com/png-clipart/20230430/original/pngtree-cute-cat-png-image_9126653.png


5. 新聞搜尋 :  

呼叫 DDGS 物件的 news() 方法並傳入關鍵字 (也可指定搜尋區域 region 與傳回筆數 max_results 等參數) 會傳回一個包含標題, 網址, 摘要等搜尋結果的字典串列, 例如 : 

>>> query='Taiwan'  
>>> results=ddgs.news(query, max_results=5)  
>>> type(results)  
<class 'list'>  

檢視第一個元素內容, 可見此字典包含新聞日期, 標題, 內容, 網頁網址, 圖片網址, 以及新聞來源等 : 

>>> results[0]   
{'date': '2025-02-12T01:22:00+00:00', 'title': 'Taiwan environment minister eyes insurance funds to help with green transition', 'body': "Taiwan aims to get more than $1.5 billion in investment from insurance funds to help finance the island's green energy transition as part of its climate change and carbon reduction goals, Environment Minister Peng Chi-ming has said.", 'url': 'https://www.reuters.com/sustainability/sustainable-finance-reporting/taiwan-environment-minister-eyes-insurance-funds-help-with-green-transition-2025-02-12/', 'image': 'https://www.reuters.com/resizer/v2/HAZKYEC4ONNSFIRMNE3YL3X5FE.jpg?auth=63a2bee4ac149ad73c2938a9370ebc61fbe9142fc0b572a4d3fac552c7f9ad93&height=1005&width=1920&quality=80&smart=true', 'source': 'Reuters'} 
>>> results[0].keys()   
dict_keys(['date', 'title', 'body', 'url', 'image', 'source'])   

用迴圈列出這五筆新聞 : 

>>> i=1   
>>> for result in results:   
    print(f'{i}. {result["title"]}:\n{result["title"]}\n{result["body"]}\n')   
    i += 1    
    
1. Taiwan environment minister eyes insurance funds to help with green transition:
Taiwan environment minister eyes insurance funds to help with green transition
Taiwan aims to get more than $1.5 billion in investment from insurance funds to help finance the island's green energy transition as part of its climate change and carbon reduction goals, Environment Minister Peng Chi-ming has said.

2. Taiwan looks to buy Alaskan natural gas as it seeks to head off US tariffs:
Taiwan looks to buy Alaskan natural gas as it seeks to head off US tariffs
TAIPEI (Reuters) -Taiwan is interested in natural gas from Alaska and will continue to assess the feasibility of purchases, the economy ministry said on Monday, as the government looks to narrow the trade surplus with the United States and head off tariffs.

3. Taiwan detects 62 Chinese aircraft as US ships transit waters:
Taiwan detects 62 Chinese aircraft as US ships transit waters
Taiwan said Wednesday it detected 62 Chinese military aircraft near the self-ruled island this week, as two US ships sailed through the sensitive Taiwan Strait.

4. Chinese Military Monitored US Ship Crossing Taiwan Strait, State Media Reports:
Chinese Military Monitored US Ship Crossing Taiwan Strait, State Media Reports
(Reuters) - China's military said it organised naval and air forces to monitor the navigation operations of a U.S. destroyer and oceanographic survey ship crossing the Taiwan Strait from February 10-12, according to state broadcaster CCTV on Tuesday.

5. Hope film 'Ne Zha 2' to be screened in Taiwan island as soon as possible: spokesperson:
Hope film 'Ne Zha 2' to be screened in Taiwan island as soon as possible: spokesperson
In response to heated discussions about "Ne Zha 2" on the island of Taiwan, Zhu Fenglian, spokesperson for the State Council Taiwan Affairs Office, expressed the wish on Wednesday's press conference that the film will be screened in the island as soon as possible,


6. 影片搜尋 :  

呼叫 DDGS 物件的 videos() 方法並傳入關鍵字 (也可指定搜尋區域 region 與傳回筆數 max_results 等參數) 會傳回一個影片搜尋結果的字典串列, 例如 : 

>>> query='APT'    
>>> results=ddgs.videos(query, max_results=5)    
>>> type(results)  
<class 'list'>  
>>> len(results)     
5

檢視第一個元素內容 : 

>>> results[0]   
{'content': 'https://www.youtube.com/watch?v=ekr2nIex040', 'description': "ROSÉ & Bruno Mars - APT. Download/stream: https://rosesarerosie.lnk.to/APTID Order APT. single CD: https://rosesarerosie.lnk.to/APT-CDID 'rosie' - the first studio album by ROSÉ - out now download/stream: http://rosesarerosie.lnk.to/rosieID ROSÉ store exclusive 'rosie' vinyl, cd's, and more available now: http://rosesarerosie.lnk.to/storeID ...", 'duration': '2:54', 'embed_html': '<iframe width="1280" height="720" src="https://www.youtube.com/embed/ekr2nIex040?autoplay=1" frameborder="0" allowfullscreen></iframe>', 'embed_url': 'https://www.youtube.com/embed/ekr2nIex040?autoplay=1', 'image_token': '0be8410e804bf8c9e7c153c4f1747a34f92023b265a805e271710c9b129f9e62', 'images': {'large': 'https://tse1.mm.bing.net/th?id=OVP.u9EjUh1N1E2_3aDLeeiQNAHgFo&pid=Api', 'medium': 'https://tse1.mm.bing.net/th?id=OVP.u9EjUh1N1E2_3aDLeeiQNAHgFo&pid=Api', 'motion': 'https://tse2.mm.bing.net/th?id=OM.cEYhVc1Do-7hdA_1731902268&pid=Api', 'small': 'https://tse1.mm.bing.net/th?id=OVP.u9EjUh1N1E2_3aDLeeiQNAHgFo&pid=Api'}, 'provider': 'Bing', 'published': '2024-10-18T04:00:07.0000000', 'publisher': 'YouTube', 'statistics': {'viewCount': 1082191568}, 'title': 'ROSÉ & Bruno Mars - APT. (Official Music Video)', 'uploader': 'ROSÉ'}

檢視字典的鍵 : 

>>> results[0].keys()    
dict_keys(['content', 'description', 'duration', 'embed_html', 'embed_url', 'image_token', 'images', 'provider', 'published', 'publisher', 'statistics', 'title', 'uploader'])

其中 content 為影片網址, title 是影片標題, embed_html 是影片嵌入網頁碼, embed_url 是影片嵌入網址, duration 是影片長度, images 是影片不同解析度的縮圖 (有 small, medium, 與 large 等尺寸) 網址, published 是發佈時間等等. 





用迴圈列出這五筆影片搜尋結果 :

>>> i=1   
>>> for result in results:  
    print(f'{i}. {result["title"]}:\n{result["content"]}')   
    i += 1   
    
1. ROSÉ & Bruno Mars - APT. (Official Music Video):
https://www.youtube.com/watch?v=ekr2nIex040
2. ROSÉ & Bruno Mars - APT. (live from 2024 MAMA AWARDS):
https://www.youtube.com/watch?v=Jn8KvdWagfo
3. ROSÉ - APT. in the Radio 1 Live Lounge:
https://www.youtube.com/watch?v=Lwv254J8Ozk
4. APT Bruno Mars and Rosé | Learn the Viral Dance | Step-by-Step Beginner Tutorial:
https://www.youtube.com/watch?v=XxOAEpW8rPs
5. APT Bruno Mars and Rosé | Learn the Viral Dance | Practice it with Music:
https://www.youtube.com/watch?v=Cysgn9xOt-Q


7. 聊天機器人 :  

呼叫 DDGS 物件的 chat() 方法並傳入提示詞會傳回一個回應字串, 例如 : 

>>> prompt='Who are you?'   
>>> reply=ddgs.chat(prompt)   
>>> type(reply)   
<class 'str'>   
>>> reply  
'I am an AI language model created by OpenAI, designed to assist with a wide range of questions and tasks by providing information and generating text based on the input I receive. How can I help you today?'

問它使用甚麼模型只回答 GPT-3 :

>>> prompt='Please tell me your specific model name'    
>>> ddgs.chat(prompt)   
"I am based on OpenAI's GPT-3 model. If you have any specific questions or need assistance, feel free to ask!"

追問它版本代號是否為 gpt-3.5-turbo, 回答 Yes :

>>> prompt='Are you using gpt-3.5-turbo?'   
>>> ddgs.chat(prompt)   
'Yes, I am based on the GPT-3.5-turbo model. If you have any questions or need assistance, feel free to ask!'

用中文也可以 : 

>>> prompt='高雄有哪些代表性美食(請用繁體中文 zh-hant 回答)?'   
>>> ddgs.chat(prompt)   
'高雄是台灣的美食天堂,擁有許多代表性的美食。以下是一些高雄的特色美食:\n\n1. **六合夜市小吃**:六合夜市是高雄最著名的夜市之一,這裡有各式各樣的小吃,如鹽酥雞、蚵仔煎、魷魚羹等。\n\n2. **海鮮**:高雄靠近海邊,新鮮的海鮮是當地的特色。可以品嚐到生魚片、蒸螃蟹、烤魚等美味。\n\n3. **牛肉麵**:高雄的牛肉麵也相當有名,湯頭濃郁,牛肉軟嫩,搭配手工麵條,讓人回味無窮。\n\n4. **鳳梨酥**:這是一種受歡迎的台灣伴手禮,高雄的鳳梨酥以餡料豐富、外皮酥脆著稱。\n\n5. **草莓冰**:在夏天,草莓冰是高雄的消暑佳品,清爽的草莓搭配冰品,讓人感到清涼。\n\n6. **珍珠奶茶**:雖然珍珠奶茶在台灣各地都很流行,但高雄有許多獨特的店家,提供不同口味的珍珠奶茶。\n\n7. **豆花**:高雄的豆花口感滑嫩,常搭配花生、紅豆等配料,是一道受歡迎的甜品。\n\n這些只是高雄美食的一部分,還有許多其他美味的選擇,值得一試!'

可能需要條列的就會回應 Markdown 字串, 要用 print() 輸出才會排列整齊 : 

>>> reply=ddgs.chat(prompt)    
>>> print(reply)     
高雄是台灣的美食重鎮,擁有許多代表性的美食。以下是一些高雄的特色美食:

1. **鹽酥雞**:這道小吃在高雄的夜市非常受歡迎,外酥內嫩,搭配各種香料和調味料,口感極佳。

2. **蚵仔煎**:高雄靠近海邊,新鮮的蚵仔是這道料理的主角,搭配蛋和地瓜粉,口感滑嫩,常淋上特製醬汁。

3. **魷魚羹**:這是一道以魷魚為主料的湯品,湯頭鮮美,魷魚鮮嫩,常搭配米飯或麵條。

4. **牛肉麵**:高雄的牛肉麵以濃郁的湯頭和軟嫩的牛肉著稱,配上手工麵條,讓人一試成主顧。

5. **海鮮**:高雄的海鮮新鮮且多樣,生魚片、蒸螃蟹、烤魚等都是當地的美味選擇。

6. **草莓冰**:在炎熱的夏天,草莓冰是高雄的消暑佳品,清爽的草莓搭配冰品,讓人感到清涼。

7. **珍珠奶茶**:雖然珍珠奶茶在台灣各地都很流行,但高雄有許多獨特的店家,提供不同口味的珍珠奶茶。

8. **豆花**:高雄的豆花口感滑嫩,常搭配花生、紅豆等配料,是一道受歡迎的甜品。

這些美食只是高雄的一部分,還有許多其他美味的選擇,值得你親自去品嚐!

問它物理學的東西 :

>>> prompt='請簡單說明何謂量子糾纏?'   
>>> ddgs.chat(prompt)    
'量子糾纏是一種量子力學現象,當兩個或多個粒子以某種方式相互作用後,它們的量子狀態會變得相互依賴,即使這些粒子相隔很遠。這意味著對其中一個粒子的測量會立即影響到另一個粒子的狀態,無論它們之間的距離有多遠。\n\n簡單來說,量子糾纏使得粒子之間存在一種超越經典物理學的聯繫,這種現象挑戰了我們對於空間和時間的傳統理解。量子糾纏在量子計算和量子通信等領域具有重要的應用潛力。'

可以傳入 model 參數指定 gpt-4o-mini 模型 : 

>>> ddgs.chat(prompt, model='gpt-4o-mini')    
'量子糾纏是一種量子力學現象,當兩個或多個粒子相互作用後,它們的量子狀態會變得相互依賴。這意味著對其中一個粒子的測量會立即影響到另一個粒子的狀態,無論它們之間的距離有多遠。這種現象顯示了粒子之間存在一種超越經典物理學的聯繫,並在量子計算和量子通信等領域具有重要的應用。'

如果指定 gpt-4o 會出現錯誤, 但自動改用 gpt-4o-mini :

>>> ddgs.chat(prompt, model='gpt-4o')    
C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\duckduckgo_search\duckduckgo_search.py:167: UserWarning: model='gpt-4o' is unavailable. Using 'gpt-4o-mini'
  warnings.warn(f"{model=} is unavailable. Using 'gpt-4o-mini'", stacklevel=1)
'量子糾纏是一種量子力學現象,當兩個或多個粒子相互作用後,它們的量子狀態會變得緊密相關。這意味著對其中一個粒子的測量會立即影響到另一個粒子的狀態,即使這些粒子相隔很遠。量子糾纏挑戰了我們對於空間和時間的傳統理解,並在量子計算和量子通信等領域具有重要的應用潛力。'

傳入 context 參數可以讓模型記得前後脈絡, 但下面測試卻顯示它居然記得耶 :

>>> prompt='我有養兩隻貓, 名叫小咪與萬萬'  
>>> ddgs.chat(prompt)    
'聽起來你有兩隻可愛的貓咪!小咪和萬萬的名字都很有趣。牠們的性格如何呢?有沒有什麼特別的故事或趣事可以分享?'   
>>> prompt='我那兩隻貓名字是甚麼?'   
>>> ddgs.chat(prompt)   
'你提到的兩隻貓的名字是小咪和萬萬。牠們聽起來都很可愛!如果你有任何關於牠們的問題或想分享的故事,隨時告訴我!' 
>>> prompt='你還記得我養了幾隻貓嗎? 名字是甚麼?'   
>>> ddgs.chat(prompt)   
'你養了兩隻貓,名字分別是小咪和萬萬。如果你有更多想分享的關於牠們的故事或問題,隨時告訴我!'

哇塞! 真不錯啊! 我猜 chat() 可能內建了記憶機制.