2026年1月24日 星期六

Gemini CLI 學習筆記 : 郵遞區號查詢小幫手專案 (二)

在前一篇測試中, 我們利用提示詞讓 Gemini CLI 生成程式碼, 正確地透過 API 取得給定地址的郵遞區號, 但是生成的程式是手動另開 DOS 或 PS 視窗執行的, 其實也可以叫 Gemini CLI 幫我們測試, 它會幫我們尋找本機的 Python 環境來執行, 本篇旨在接續先前靠一張嘴寫程式的方法, 叫 AI 測試它自己寫的程式. 

本系列之前的測試文章參考 : 


PS D:\gemini> cd postal-helper-proj1   
PS D:\gemini\postal-helper-proj1> gemini   

然後輸入 /res 或 /resume 恢復之前的對話, Gemini CLI 會找出之前的 session 列表, 可以上下移動選擇要恢復的對話後按 Enter (此處只有編號 1 的對話) :




輸入 : 

> 請幫我測試程式是否正確

Gemini CLI 這次找了一個台中的地址, 執行結果為找不到, 所以它寫了一個 test_api.py 的測試程式, 要求儲存程式檔 :




接著要求執行測試程式 test_api.py : 




測試程式執行時出現錯誤, 它會自行檢查原因與修正, 它發現需修改 postal-helper.py 原始碼, 要求授權 : 




接著修正測試程式 test_api.py : 




執行結果程式已正常, 但查詢台灣大道 99 號結果仍然找不到, 可能是地址有改, 它打算換別的地址測試來測試 : 




改用中港路二段99號就順利傳回郵遞區號了 :




以上的 Gemini CLI 測試中都是使用系統 Python 來執行程式, 如果 Gemini CLI 找不到 Python 執行環境就會出現錯誤. 目前最新的 Python 版本管理工具是 uv 工具, 它整合了 Python 版本, 套件安裝, 以及虛擬環境管理於一體, 使用上比傳統 pip + venv 方便許多, 參考 :


接下來輸入 "我使用 uv 工具" 提示詞看看 Gemini CLI 如何利用 uv 來執行測試, 首先它會初始化 uv 專案, 建立專案描述檔 pyproject.toml :




接著它會編輯 pyproject.toml, 加入腳本定義, 以便能用 uv run 來執行腳本 :




接下來它要求授權修改 postal-helper.py 腳本 : 




程式已改好, 要求授權用 uv run 執行腳本, 傳入地址參數為台北市信義區市府路 1 號 : 





uv run 指令執行成功, 取得正確 6 碼郵遞區號 110204 : 




最後, 我把 Gemini CLI 修改過的 postal-helper.py 抄錄如下 :

import sys
import json
import urllib.parse
import urllib.request
import io

# 確保在 Windows 上輸出為 UTF-8
if sys.platform == "win32" and isinstance(sys.stdout, io.TextIOWrapper) and sys.stdout.encoding != 'utf-8':
    try:
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
    except (AttributeError, io.UnsupportedOperation):
        pass

def get_postal_code(address):
    base_url = "https://zip5.5432.tw/zip5json.py"
    params = {'adrs': address}
    # Ensure the URL is correctly encoded
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    
    try:
        # User requested 6-digit postal code
        print(f"正在查詢地址: {address} ...")
        
        req = urllib.request.Request(url)
        # Some APIs require a User-Agent
        req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) PostalHelper/1.0')
        
        with urllib.request.urlopen(req) as response:
            if response.status != 200:
                print(f"錯誤: API 返回狀態碼 {response.status}")
                return

            data = response.read()
            try:
                result = json.loads(data)
            except json.JSONDecodeError:
                print("錯誤: 無法解析 JSON 回應。")
                print(f"回應內容: {data.decode('utf-8', errors='ignore')}")
                return
            
            # The API returns 'zipcode6' for the 6-digit code.
            # Sometimes it might return an empty string or null if not found.
            zip6 = result.get('zipcode6')
            
            if zip6:
                print("-" * 30)
                print(f"地址: {result.get('address', address)}")
                print(f"6碼郵遞區號: {zip6}")
                print("-" * 30)
            else:
                print("找不到該地址的 6 碼郵遞區號。")
                # Show full result just in case useful info is elsewhere
                if 'error' in result:
                    print(f"API 錯誤: {result['error']}")

    except urllib.error.URLError as e:
        print(f"網絡錯誤: {e}")
    except Exception as e:
        print(f"發生未預期的錯誤: {e}")

def main():
    if len(sys.argv) > 1:
        # Join arguments to handle addresses with spaces if not quoted
        address_input = " ".join(sys.argv[1:])
        get_postal_code(address_input)
    else:
        # Interactive mode
        print("台灣 6 碼郵遞區號小幫手 (uv 版)")
        print("用法: uv run postal-helper <地址>")
        print("或在下方輸入地址 (按 Ctrl+C 離開):")
        while True:
            try:
                address_input = input("\n請輸入地址: ").strip()
                if not address_input:
                    continue
                get_postal_code(address_input)
            except KeyboardInterrupt:
                print("\n程式結束。")
                break
            except EOFError:
                break

if __name__ == "__main__":
    main()

沒有留言 :