2025年9月30日 星期二

2025 年第 39 周記事

上周休了 8 天去日本旅行, 回來後趕緊地繼續把已註冊的 Hahow 企業版課程上完, 因為 9 月底使用期限就要終止啦! 其中有門旅遊日文課程對未來幾年去日本深度旅遊很有幫助, 真是相見恨晚啊! 周末因教師節國定假日連放三天, 我都在鄉下家上課上好上滿. 

自從上回颱風過後, 芭樂樹似乎進入休眠期, 已兩個月未見開花, 準備趁這段時間進行修枝, 但以前放在庫房的柴刀卻騙尋不著, 周日去市集經過小漢時進去花了 300 元買了一把鋒利的新柴刀, 昨天早上趁補假去墓園拿參道上的比人高雜草試刀, 新刀果真無往不利啊! 傍晚本想要拿刀去修枝, 但因為要準備晚餐, 只好延至下周末啦!

2025年9月29日 星期一

好站 : Python LINE BOT 串接 OpenAI ChatGPT

今天在整理鄉下老家 LEMEL 老電腦上的書籤時 (2GB DRAM 效能很差, 開太多頁籤會把記憶體吃完), 發現了很久以前搜尋留下的這個網頁 : 


這正是我下一步要測試的項目, 存參. 

這篇其實是 OXXO 參加 iT 邦幫忙的 "跟著 OXXO 一起學 Python 系列" 中的一篇, 我買過也借過他寫的書, 內容都非常實用. 

LangChain 學習筆記索引

我在 2024 年底的內訓課程開始接觸 LangChain, 也看了一陣子書, 但一直到今年 (2025) 5 月初做完 OpenAI API 基本測試才開始安裝套件並動手測試. 最近從關西之旅回來後開始進行模型串接測試, 為了查考方便將筆記做成索引如下 :   



~進行中~

參考書籍 :

2025年9月28日 星期日

LangChain 學習筆記 : 串接 LLM 模型 (二)

晚上繼續測試 LangChain, 本篇旨在測試串接谷歌 Gemini 的方法.

本系列之前的文章參考 :


關於谷歌原生的 Gemini 串接套件 google-generativeai 用法參考 :



5. 安裝 langchain-google-genai 套件 :    

LangChain 用來串接 Gemini 的套件為 langchain-google-genai, 與前一篇串接 GPT 模型一樣, langchain-google-genai 套件只是封裝了透過谷歌原生套件 google-generativeai 串接 Gemini 模型的介面而已, 它無法單獨與 Gemini 聊天, 必須搭配 google-generativeai 當底層介面才行. 

不過, 由於 langchain-google-genai 與 google-generativeai 這兩個套件在安裝時會與相依套件 google-ai-generativelanguage 有版本衝突問題 (安裝時會出現 ERROR 訊息), 我向 ChatGPT 尋求解決方案, 經測試發現只要指定特定版本即可順利安裝, 若已安裝此兩套件出現錯誤, 請先用下列指令解除安裝 :

pip uninstall -y google-ai-generativelanguage google-generativeai langchain-google-genai  

D:\python\test>pip uninstall -y google-ai-generativelanguage google-generativeai langchain-google-genai
Found existing installation: google-ai-generativelanguage 0.7.0
Uninstalling google-ai-generativelanguage-0.7.0:
  Successfully uninstalled google-ai-generativelanguage-0.7.0
Found existing installation: google-generativeai 0.8.5
Uninstalling google-generativeai-0.8.5:
  Successfully uninstalled google-generativeai-0.8.5
Found existing installation: langchain-google-genai 2.1.12
Uninstalling langchain-google-genai-2.1.12:
  Successfully uninstalled langchain-google-genai-2.1.12

然後指定安裝 0.6.15 版的 google-ai-generativelanguage : 

pip install google-ai-generativelanguage==0.6.15    

D:\python\test>pip install google-ai-generativelanguage==0.6.15   
Collecting google-ai-generativelanguage==0.6.15
  Using cached google_ai_generativelanguage-0.6.15-py3-none-any.whl.metadata (5.7 kB)
Requirement already satisfied: google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (2.19.2)
Requirement already satisfied: google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from google-ai-generativelanguage==0.6.15) (2.22.0)
Requirement already satisfied: proto-plus<2.0.0dev,>=1.22.3 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-ai-generativelanguage==0.6.15) (1.24.0)
Requirement already satisfied: protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-ai-generativelanguage==0.6.15) (5.28.1)
Requirement already satisfied: googleapis-common-protos<2.0.dev0,>=1.56.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (1.65.0)
Requirement already satisfied: requests<3.0.0.dev0,>=2.18.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (2.31.0)
Requirement already satisfied: grpcio<2.0dev,>=1.33.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (1.66.1)
Requirement already satisfied: grpcio-status<2.0.dev0,>=1.33.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (1.66.1)
Requirement already satisfied: cachetools<6.0,>=2.0.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1->google-ai-generativelanguage==0.6.15) (5.3.1)
Requirement already satisfied: pyasn1-modules>=0.2.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1->google-ai-generativelanguage==0.6.15) (0.3.0)
Requirement already satisfied: rsa<5,>=3.1.4 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1->google-ai-generativelanguage==0.6.15) (4.9)
Requirement already satisfied: six>=1.9.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1->google-ai-generativelanguage==0.6.15) (1.17.0)
Requirement already satisfied: urllib3<2.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1->google-ai-generativelanguage==0.6.15) (1.26.19)
Requirement already satisfied: charset-normalizer<4,>=2 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests<3.0.0.dev0,>=2.18.0->google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (3.2.0)
Requirement already satisfied: idna<4,>=2.5 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests<3.0.0.dev0,>=2.18.0->google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (3.4)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from requests<3.0.0.dev0,>=2.18.0->google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0dev,>=1.34.1->google-ai-generativelanguage==0.6.15) (2025.6.15)
Requirement already satisfied: pyasn1>=0.1.3 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from rsa<5,>=3.1.4->google-auth!=2.24.0,!=2.25.0,<3.0.0dev,>=2.14.1->google-ai-generativelanguage==0.6.15) (0.5.0)
Using cached google_ai_generativelanguage-0.6.15-py3-none-any.whl (1.3 MB)
Installing collected packages: google-ai-generativelanguage
Successfully installed google-ai-generativelanguage-0.6.15

其次安裝 0.8.5 版的 google-generativeai : 

pip install google-generativeai==0.8.5 --no-deps

D:\python\test>pip install google-generativeai==0.8.5 --no-deps  
Collecting google-generativeai==0.8.5
  Using cached google_generativeai-0.8.5-py3-none-any.whl.metadata (3.9 kB)
Using cached google_generativeai-0.8.5-py3-none-any.whl (155 kB)
Installing collected packages: google-generativeai
Successfully installed google-generativeai-0.8.5

最後安裝 2.1.12 版的 langchain-google-genai : 

pip install langchain-google-genai==2.1.12 --no-deps

D:\python\test>pip install langchain-google-genai==2.1.12 --no-deps   
Collecting langchain-google-genai==2.1.12
  Using cached langchain_google_genai-2.1.12-py3-none-any.whl.metadata (7.1 kB)
Using cached langchain_google_genai-2.1.12-py3-none-any.whl (50 kB)
Installing collected packages: langchain-google-genai
Successfully installed langchain-google-genai-2.1.12


6. 檢視 langchain-google-genai 套件內容 :    

匯入 langchain_google_genai 後用 dir() 檢視其內容 : 

>>> import langchain_google_genai     
>>> dir(langchain_google_genai)   
['AqaInput', 'AqaOutput', 'ChatGoogleGenerativeAI', 'DoesNotExistsException', 'GenAIAqa', 'GoogleGenerativeAI', 'GoogleGenerativeAIEmbeddings', 'GoogleVectorStore', 'HarmBlockThreshold', 'HarmCategory', 'Modality', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '_common', '_enums', '_function_utils', '_genai_extension', '_image_utils', 'chat_models', 'embeddings', 'genai_aqa', 'google_vector_store', 'llms']

其中的 ChatGoogleGenerativeAI 類別便是用來串接 Gemini 模型用的, 其建構式 ChatGoogleGenerativeAI() 的參數如下表所示 : 


 參數名稱  說明
model Gemini 模型名稱,例如 "gemini-1.5-pro",對應 API 的 model 參數
temperature 生成隨機性,範圍 0~1,對應 API 的 temperature
max_output_tokens 最大生成 token 數量,對應 API 的 max_output_tokens
top_p nucleus sampling 控制生成多樣性,對應 API 的 top_p
top_k top-k sampling 控制生成多樣性,對應 API 的 top_k
api_key Google API Key,可直接傳入或使用環境變數 GOOGLE_API_KEY
request_timeout 請求超時秒數,對應 SDK 的 timeout / request_timeout
model_kwargs 可傳遞額外參數給 Gemini SDK,例如 stop_sequences、candidate_count 等
verbose 布林值,是否印出 debug 訊息
client 可傳自訂 Gemini SDK client,一般不用


在互動環境測試如下 :

我已將 Gemini API Key 儲存在環境變數檔案 .env 裡面 (名稱為 'GEMINI_API_KEY'), 先用 dotenv 套件讀取出來放在 gemini_api_key 變數中 :

>>> from dotenv import dotenv_values    
config=dotenv_values('.env')     
gemini_api_key=config.get('GEMINI_API_KEY')    

從 langchain_google_genai 套件中匯入 ChatGoogleGenerativeAI 類別 :  

>>> from langchain_google_genai import ChatGoogleGenerativeAI   

然後呼叫 ChatGoogleGenerativeAI() 建構函式並傳入 api_key 與 model 參數來建立 ChatGoogleGenerativeAI 物件 : 

>>> chat_model=ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.5-flash')    
>>> type(chat_model)         
<class 'langchain_google_genai.chat_models.ChatGoogleGenerativeAI'>   

注意, 目前可用的 Gemini 模型如下 (之前測試使用的 1.5 版已下架, 無法串接了, 如果 model 指定舊版 1.5 版會得到 404 錯誤) : 


模型名稱 描述 適合場景 穩定性
gemini-2.5-flash 快速、低延遲的輕量模型 聊天、即時回應 穩定
gemini-2.5-pro 先進推理模型,支援長上下文 複雜問題、程式碼生成 穩定
gemini-2.5-flash-lite 更輕量的 Flash 變體 行動裝置、成本敏感應用 穩定
gemini-2.0-flash 舊 2.0 版本,快速但較舊 基本任務 穩定


這樣就可以呼叫 ChatGoogleGenerativeAI 物件的 invoke() 方法並傳入提示詞來起始一個對話 : 

>>> response=chat_model.invoke('你是誰?')     
>>> type(response)     
<class 'langchain_core.messages.ai.AIMessage'>     

檢視 invoke() 傳回的 AIMessage 物件內容 : 

>>> response   
AIMessage(content='我是一个大型语言模型,由 Google 训练。', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.5-flash', 'safety_ratings': []}, id='run--9c115a61-fc2d-4c74-a611-a7da5465b65b-0')

可見模型的回應是放在其 content 屬性內 : 

>>> response.content     

'我是一个大型语言模型,由 Google 训练。'

由這兩篇測試可知 LangChain 的好處是對於不同的模型都可以用相同的 API 與模型交談. 

LangChain 學習筆記 : 串接 LLM 模型 (一)

最近打算進行在本機安裝 llama 模型的學習測試, 但必須先學會 LangChain 才好上手. 之前已在筆電中安裝好 LangChain, 接下來要來測試它調用 LLM 模型的方法. 

本系列之前的文章參考 :



1. 安裝 LLM 原生 API 套件與其 LangChain 橋接套件 :    

本篇測試聚焦在 OpenAI 與 Gemini 這兩種 LLM 模型, 所以在使用 LangChain 呼叫 LLM 模型之前必須先安裝 LLM 的原生 API 套件與其 LangChain 橋接套件. 這是因為 LangChain 遵循不重複造輪子原則, 仍然依賴原生 API 來與模型做底層溝通, 不過 LangChain 另外寫一個橋接套件來封裝以統一各模型之調用介面. 

LangChain 在 2024 年升版至 v1.0 進行了模組化重構以便使核心輕量化, 將許多大型 LLM 提供商例如 OpenAI, Anthropic, Google 等公司的模型橋接介面移出主套件, 將它們拆成獨立的套件以利維護, 所以安裝因此使用 LangChain 與各 LLM 介接前須各自安裝該 LLM 的介面套件, 例如 :

pip install langchain-openai  (介接 OpenAI 的 GPT 模型)
pip install langchain-google-genai  (介接 Google 的 Gemini 模型) 

先來測試 OpenAI 的介面套件. 


2. 安裝 langchain-openai 套件 :      

由於 langchain-openai 套件只是封裝了透過 openai 套件串接 GPT 模型的介面而已, 它無法單獨串接 OpenAI 模型, 必須先用下列指令安裝原生的 openai 套件才行 : 

pip install openai   

參考 :


然後用 pip 安裝 langchain-openai 套件 : 

pip install langchain-openai    

D:\python\test>pip install langchain-openai   
Collecting langchain-openai
  Downloading langchain_openai-0.3.16-py3-none-any.whl.metadata (2.3 kB)
Requirement already satisfied: langchain-core<1.0.0,>=0.3.58 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-openai) (0.3.58)
Collecting openai<2.0.0,>=1.68.2 (from langchain-openai)
  Downloading openai-1.77.0-py3-none-any.whl.metadata (25 kB)
Requirement already satisfied: tiktoken<1,>=0.7 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-openai) (0.8.0)
Requirement already satisfied: langsmith<0.4,>=0.1.125 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (0.3.42)
Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (8.2.3)
Requirement already satisfied: jsonpatch<2.0,>=1.33 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (1.33)
Requirement already satisfied: PyYAML>=5.3 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (6.0.1)
Requirement already satisfied: packaging<25,>=23.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (24.2)
Requirement already satisfied: typing-extensions>=4.7 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (4.12.2)
Requirement already satisfied: pydantic<3.0.0,>=2.5.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langchain-core<1.0.0,>=0.3.58->langchain-openai) (2.11.4)
Requirement already satisfied: jsonpointer>=1.9 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from jsonpatch<2.0,>=1.33->langchain-core<1.0.0,>=0.3.58->langchain-openai) (2.4)
Requirement already satisfied: httpx<1,>=0.23.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (0.28.1)
Requirement already satisfied: orjson<4.0.0,>=3.9.14 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (3.10.18)
Requirement already satisfied: requests<3,>=2 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (2.31.0)
Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (1.0.0)
Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (0.23.0)
Requirement already satisfied: anyio in c:\users\tony1\appdata\roaming\python\python310\site-packages (from httpx<1,>=0.23.0->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (3.7.1)
Requirement already satisfied: certifi in c:\users\tony1\appdata\roaming\python\python310\site-packages (from httpx<1,>=0.23.0->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (2023.7.22)
Requirement already satisfied: httpcore==1.* in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from httpx<1,>=0.23.0->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (1.0.7)
Requirement already satisfied: idna in c:\users\tony1\appdata\roaming\python\python310\site-packages (from httpx<1,>=0.23.0->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (3.4)
Requirement already satisfied: h11<0.15,>=0.13 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from httpcore==1.*->httpx<1,>=0.23.0->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (0.14.0)
Requirement already satisfied: distro<2,>=1.7.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from openai<2.0.0,>=1.68.2->langchain-openai) (1.9.0)
Requirement already satisfied: jiter<1,>=0.4.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from openai<2.0.0,>=1.68.2->langchain-openai) (0.8.0)
Requirement already satisfied: sniffio in c:\users\tony1\appdata\roaming\python\python310\site-packages (from openai<2.0.0,>=1.68.2->langchain-openai) (1.3.0)
Requirement already satisfied: tqdm>4 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from openai<2.0.0,>=1.68.2->langchain-openai) (4.66.1)
Requirement already satisfied: exceptiongroup in c:\users\tony1\appdata\roaming\python\python310\site-packages (from anyio->httpx<1,>=0.23.0->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (1.1.3)
Requirement already satisfied: annotated-types>=0.6.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<1.0.0,>=0.3.58->langchain-openai) (0.7.0)
Requirement already satisfied: pydantic-core==2.33.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<1.0.0,>=0.3.58->langchain-openai) (2.33.2)
Requirement already satisfied: typing-inspection>=0.4.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<1.0.0,>=0.3.58->langchain-openai) (0.4.0)
Requirement already satisfied: charset-normalizer<4,>=2 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests<3,>=2->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (3.2.0)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from requests<3,>=2->langsmith<0.4,>=0.1.125->langchain-core<1.0.0,>=0.3.58->langchain-openai) (1.26.19)
Requirement already satisfied: regex>=2022.1.18 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from tiktoken<1,>=0.7->langchain-openai) (2023.12.25)
Requirement already satisfied: colorama in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from tqdm>4->openai<2.0.0,>=1.68.2->langchain-openai) (0.4.6)
Downloading langchain_openai-0.3.16-py3-none-any.whl (62 kB)
Downloading openai-1.77.0-py3-none-any.whl (662 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 662.0/662.0 kB 4.3 MB/s eta 0:00:00
Installing collected packages: openai, langchain-openai
  Attempting uninstall: openai
    Found existing installation: openai 1.57.2
    Uninstalling openai-1.57.2:
      Successfully uninstalled openai-1.57.2
Successfully installed langchain-openai-0.3.16 openai-1.77.0


3. 檢視 langchain-openai 套件內容 :      

匯入 langchain_openai 後用 dir() 檢視內容 :

>>> import langchain_openai   
>>> dir(langchain_openai)    
['AzureChatOpenAI', 'AzureOpenAI', 'AzureOpenAIEmbeddings', 'ChatOpenAI', 'OpenAI', 'OpenAIEmbeddings', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'chat_models', 'embeddings', 'llms']

可見此套件包含了 OpenAI 的介面類別與其 Azure 版本, 以下測試僅使用 OpenAI 版本的 ChatOpenAI 類別來與 OpenAI API 進行對話 : 
  • ChatOpenAI : 
    此類別封裝 OpenAI 的 Chat Completions API (GPT-3.5 / GPT-4 系列), 可用於聊天式應用, 例如對話代理, 工具鏈等, 對應 LangChain 的 BaseChatModel.
  • OpenAI : 
    此類別封裝 OpenAI 的 Completion API (較舊的文字生成 API), 適合非對話式生成, 例如單純文字續寫, 對應 LangChain 的 LLM.
  • OpenAIEmbeddings : 
    此類別封裝 OpenAI 的 Embeddings API (text-embedding-ada-002 等), 用於將文字轉換為向量, 可搭配向量資料庫進行相似度搜尋.
ChatOpenAI 類別封裝的是 OpenAI 的 Chat Completions API, 所以建構式 ChatOpenAI() 有部分的參數與 OpenAI API 的參數是共通的 (但名稱可能稍微不同), 如下表所示 : 


 共通參數名稱  說明
 api_key  OpenAI API 金鑰
 model  指定模型名稱,例如 "gpt-4o-mini"
 temperature  控制生成隨機性,數值越高輸出越多樣化
 max_completion_tokens  限制最大回覆 token 數量(OpenAI API 參數名 : max_tokens
 stop  設定停止字串,模型生成到此字串會自動停止
 n  一次生成多少個候選回覆
 streaming  是否使用串流模式返回回應(OpenAI API 參數名 : stream
 logprobs / top_logprobs  是否回傳 token 機率與最可能的候選 token
 response_format  指定回覆格式,例如 JSON 模式
 reasoning_effort  指定 reasoning 模型的運算強度
 request_timeout  設定請求逾時(OpenAI API 參數名 : timeout
 openai_api_base  自訂的 API 端點(OpenAI API 參數名 : base_url


下表則是 LangChain 的 ChatOpenAI() 特有的額外參數 (OpenAI API 沒有的) : 


 參數名稱  說明
 openai_organization  指定組織 ID(OpenAI API 須透過支援環境變數)
 openai_proxy  設定 HTTP 代理伺服器
 max_retries  舍訂請求失敗時的重試次數(OpenAI API 需自己實作)
 tags  用於追蹤或 logging 的標籤
 tiktoken_model_name  指定用於 token 計算的模型名稱
 model_kwargs  以字典形式傳遞額外 OpenAI API 參數 (top_pstop 等)
 stream_usage  若啟用 streaming,是否回傳使用量資訊
 reasoning  傳入 dict 形式的 reasoning 設定,用於特定 reasoning 模型
 use_responses_api  是否使用新版 Responses API 取代傳統 Chat API
 output_version  控制 AIMessage 輸出格式版本,例如 'v0' 或 'responses/v1'
 use_previous_response_id  在 Responses API 模式下是否攜帶前次回應 ID 以延續上下文


其中 model_kwargs 參數用來傳遞 OpenAI API 支持但 ChatOpenAI() 沒有直接支持的參數, 這些 OpenAI API 參數要以字典形式 (鍵為參數名稱例如 'top_p') 透過 model_kwargs 參數傳給 ChatOpenAI(). 這些參數鍵如下表 : 


 model_kwargs 參數的鍵  說明
 top_p  控制生成隨機性(0~1),與 temperature 配合使用
 presence_penalty  對新主題或新 token 的偏好程度,增加可產生更多新內容
 frequency_penalty  避免重複 token 的懲罰值,數值越高,重複越少
 logit_bias  調整特定 token 出現機率的字典
 user  指定用戶 ID,用於 OpenAI 端追蹤或使用統計
 stop  停止字串或字串列表,模型生成遇到即可停止
 functions  定義可供模型呼叫的函式列表,用於 function-calling 功能
 function_call  控制函式呼叫行為,例如 "auto", "none" 或指定函式名稱
 max_tokens  限制生成 token 數量
 logprobs  回傳每個 token 的 log 機率資訊,可用於分析或生成控制


4. 用 langchain-openai 套件串接 OpenAI 模型 :      

串接 OpenAI 的 GPT 模型需要 API Key, 我已將其儲存在環境變數檔案 .env 裡面, 先用 dotenv 套件讀取出來放在 openai_api_key 變數裡 :

>>> from dotenv import dotenv_values    
config=dotenv_values('.env')     
openai_api_key=config.get('OPENAI_API')    

匯入 langchain-openai.ChatOpenAI 類別 : 

>>> from langchain_openai import ChatOpenAI   

然後呼叫 ChatOpenAI() 建構式並傳入 api_key 與 model 參數來建立 ChatOpenAI 物件 : 

>>> chat_model=ChatOpenAI(api_key=openai_api_key, model='gpt-3.5-turbo')    
>>> type(chat_model)     
<class 'langchain_openai.chat_models.base.ChatOpenAI'>    

這樣就可以呼叫此 ChatOpenAI 物件的 invoke() 方法並傳入提示詞來起始一個對話 : 

>>> response=chat_model.invoke('你是誰?')  
>>> type(response)  
<class 'langchain_core.messages.ai.AIMessage'>  

可見 invoke() 方法會傳回一個 AIMessage 物件, 檢視此物件內容 : 

>>> response     
AIMessage(content='我是AI助手。有什麼問題我可以幫助您解答?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 12, 'total_tokens': 39, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'id': 'chatcmpl-CKgsNf6T6qnUtam5GJtJevTm9Jk1J', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--3abc232a-90da-44d1-870b-41a0c6ca4b03-0', usage_metadata={'input_tokens': 12, 'output_tokens': 27, 'total_tokens': 39, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})

模型的回應放在其 content 屬性內 : 

>>> response.content   
'我是AI助手。有什麼問題我可以幫助您解答?'

台股價量資料套件 twstock 再包裝 (二)

在前一篇測試中, 我們撰寫了一個自訂模組 twstock_data.py, 其中的 download() 函式模仿 yfinance.download() 透過 twstock 模組取得最近 45 天的台股量價資料, 回傳格式也是仿新版 yfinance 的雙層欄位的 DataFrame, 參考 : 


本篇則是要擴充 twstock_data.py 模組, 添加如下實用函式 : 
  • available_range(stock) : 傳回指定股票的可用日期範圍
  • available_ranges(stocks) :  傳回多支股票 (串列) 的可用日期範圍 (串列)
  • latest_close(stock) : 指定股票的最新收盤價
程式碼修改如下 : 

# twstock_data.py
import twstock
import pandas as pd
from datetime import datetime

def download(stocks, start=None, end=None):
    """
    模擬 yfinance.download(),使用 twstock 抓取台股資料
    stocks: '0050.tw' 或 ['0050.tw','0056.tw'], '0050' 或 ['0050','0056']
    start, end: 'YYYY-MM-DD' 字串
    回傳 DataFrame,欄位結構完全模擬 yfinance (MultiIndex: Price, Ticker)
    """
    if isinstance(stocks, str):    # 判斷標的是單支或多支股票
        stocks=[stocks]    # 若是單支則將其放入串列
    all_data=[]    # 儲存所有價量資料的串列
    for stock_code in stocks:    # 走訪所有標的
        code=stock_code.replace('.tw', '').replace('.TW','')    # 去除 tw/TW
        stock=twstock.Stock(code)    # 建立 Stock 物件
        # 收集資料
        data=[]    # 儲存打包後的股票價量資料的串列
        for date, o, h, l, c, v in zip(stock.date, stock.open, stock.high, stock.low, stock.close, stock.capacity):    # 打包成 tuple 後存入串列, 最後轉成 DataFrame
            data.append((date, o, h, l, c, v, c))  # 最後一個 c 當 Adj Close
        df=pd.DataFrame(data, columns=['Date','Open','High','Low','Close','Volume','Adj Close'])
        df.set_index('Date', inplace=True)    # 設定索引
        # 篩選日期
        if start:    # 有傳入起始日期
            start_date=datetime.strptime(start, '%Y-%m-%d')
            df=df[df.index >= start_date]
        if end:    # 有傳入結束日期
            end_date=datetime.strptime(end, '%Y-%m-%d')
            df=df[df.index <= end_date]
        # 建立 MultiIndex 欄位 (Price, Ticker)
        df.columns=pd.MultiIndex.from_product([df.columns, [stock_code.upper()]], names=["Price", "Ticker"])
        all_data.append(df)
    # 合併多檔股票
    result=pd.concat(all_data, axis=1).sort_index(axis=1, level=0)
    return result

def available_range(stock):
    """回傳單支股票可用日期範圍"""
    code=stock.replace('.tw', '').replace('.TW','')
    st=twstock.Stock(code)
    return st.date[0], st.date[-1]

def available_ranges(stocks):
    """回傳多支股票的可用日期範圍 dict"""
    if isinstance(stocks, str):
        code=stocks.replace('.tw', '').replace('.TW','')
        stocks=[code]
    result={}
    for s in stocks:
        code=s.replace('.tw', '').replace('.TW','')
        st=twstock.Stock(code)
        result[s]=(st.date[0], st.date[-1])
    return result

def latest_close(stock):
    """回傳最新收盤價"""
    code=stock.replace('.tw', '').replace('.TW','')
    st=twstock.Stock(code)
    return st.price[-1]

在互動環境測試如下 :

>>> import twstock_data as td   

呼叫 available_range() 傳入單一股票會傳回起訖日期的 datetime 物件 tuple : 

>>> td.available_range('0050.tw')   
(datetime.datetime(2025, 8, 15, 0, 0), datetime.datetime(2025, 9, 26, 0, 0))

呼叫 available_ranges() 傳入多支股票串列會傳回各標的之起訖日期字典 :

>>> td.available_ranges(['0050.tw', '2330.tw'])   
{'0050.tw': (datetime.datetime(2025, 8, 15, 0, 0), datetime.datetime(2025, 9, 26, 0, 0)), '2330.tw': (datetime.datetime(2025, 8, 15, 0, 0), datetime.datetime(2025, 9, 26, 0, 0))}

呼叫 latest_close() 傳入單一股票會傳回最近之收盤價 : 

>>> td.latest_close('0050.tw')   
57.25

依據上面 available_range() 的起訖日期下載價量資料 : 

>>> df=td.download('0050.tw', start='2025-08-15', end='2025-09-26')    
>>> df  
Price      Adj Close   Close    High     Low    Open     Volume
Ticker       0050.TW 0050.TW 0050.TW 0050.TW 0050.TW    0050.TW
Date                                                           
2025-08-15     53.15   53.15   53.30   52.80   53.15   55175623
2025-08-18     53.30   53.30   53.35   52.80   52.95   48004775
2025-08-19     53.20   53.20   53.30   53.05   53.30   31232902
2025-08-20     51.70   51.70   52.60   51.50   52.55  241376498
2025-08-21     52.00   52.00   52.10   51.75   51.75   76525276
2025-08-22     51.75   51.75   52.10   51.65   52.05   47853232
2025-08-25     52.70   52.70   52.95   52.35   52.45   64451775
2025-08-26     52.75   52.75   52.85   52.30   52.55   42402036
2025-08-27     53.00   53.00   53.15   52.90   53.00   91463591
2025-08-28     52.50   52.50   52.90   52.50   52.80   61856984
2025-08-29     52.50   52.50   52.95   52.50   52.80   37614042
2025-09-01     52.00   52.00   52.45   51.75   52.15   82748402
2025-09-02     52.00   52.00   52.50   51.95   52.30   34866736
2025-09-03     51.95   51.95   52.20   51.75   51.90   41247523
2025-09-04     52.30   52.30   52.70   52.25   52.55   38611419
2025-09-05     52.85   52.85   52.90   52.65   52.70   38976905
2025-09-08     53.35   53.35   53.55   53.25   53.40   61781569
2025-09-09     53.90   53.90   53.95   53.50   53.55   57948913
2025-09-10     54.95   54.95   55.00   54.40   54.40   75585424
2025-09-11     55.45   55.45   55.90   55.25   55.55   87937880
2025-09-12     56.00   56.00   56.00   55.70   55.75   59101728
2025-09-15     56.00   56.00   56.05   55.80   56.00   56031630
2025-09-16     56.75   56.75   56.85   56.15   56.15   65388179
2025-09-17     56.35   56.35   56.60   56.25   56.60   70590054
2025-09-18     56.95   56.95   56.95   56.25   56.50   45899106
2025-09-19     57.00   57.00   57.10   56.60   57.00   74098514
2025-09-22     57.40   57.40   57.40   56.75   57.00   59170020
2025-09-23     58.45   58.45   58.60   57.85   57.85   92241987
2025-09-24     58.40   58.40   58.95   58.10   58.70   95128957
2025-09-25     58.20   58.20   58.40   58.00   58.35   59962136
2025-09-26     57.25   57.25   57.90   56.85   57.90  170962034
>>>

可見最近一個交易日 9/26 的收盤價確實是 57.25.

2025年9月26日 星期五

備份樹莓派 Pi 3A+ 系統 (MicroSD 卡)

上個月完成 Pi 3A+ 系統設定後一直沒時間將整個 32GB 的 Raspbian Buster 作業系統備份成 .img 映像檔保存, 晚上趁回鄉下前整理房間, 筆電沒在用時以 Win32 Disk Imager 備份, 作法參考 :


這樣一旦系統崩掉時就可以馬上從映像檔回復囉, 省時省工. 

好書 : LLM 核心攻略制霸生成式 AI

這本碁峰出版的好書借自母校圖書館, 翻譯自 "Quick Guide to Large Language Models 2nd Edition" (Pearson 2024), 作者 Sinan Ozdemir 以淺顯易懂筆法介紹 LLM 的基本概念, 運作原理, 與使用 LLM 的所有必要資訊, 只要懂 Python 語言就能兼顧理論與實作, 深入理解 LLM 與學會如何操作LLM; 當然也可以完全不寫一行程式碼, 只閱讀理論部分.  


Source : 天瓏書店


本書的程式碼可從 GitHub 下載 :

# https://github.com/sinanuozdemir/quick-start-guide-to-llm

以下是我的讀書札記 :

第一章 :
  • 雖然 LLM 看似能理解與生成自然語言的文本, 擅長執行文本分類, 情感分析與具名實體識別等 NLP 任務, 但它們其實無法像人類一樣真正理解語言.
  • Transformer 的成功主要來自於注意力機制, 遷移學習, 大型神經網路等同時發生了突破性的進展所致. 
  • Transformer 架構的特點是可以大規模地平行執行與擴展, 這是以前的先進 NLP 模型無法做到的. Transformer 使用一種稱為 self-attention 的注意力計算方法, 讓序列中的每一個單字可以關注 (查看前後文) 序列中的所有其他單字以捕捉大範圍的相依性, 以及單字之間的語境 (context) 關係. 
  • Transformer 與其他深度學習架構最大的不同在於它使用注意力機制來捕捉詞元之間的長距離依賴關係. 在注意力機制普及以前, 大多數的神經網路對所有輸入都是一視同仁; 而注意力機制則是可以動態地聚焦於輸入序列的不同部分
  • 語言建模 (language modeling) 是 NLP 的一個子領域, 其模型通常有兩種 : 自編碼 (autoencoding) 與自回歸 (autoregressive). 
  • 自回歸模型的目的是基於前面的詞元來預測句子的下一個詞元, 此模型可對應到 Transformer 的解碼器部分. 解碼器使用一個遮罩來遮住句子, 讓注意力機制只看到之前的詞元. 自回歸模型適合用來生成文本, 例如 GPT.
  • 自編碼模型用來為 "被破壞的輸入" 重建句子, 此模型可對應到 Transformer 的編碼器部分, 它可讀取完整的輸入, 無須任何遮罩. 自編碼模型會建立整個句子的雙向表示法, 主要應用在文本或詞元分類, 例如 BERT.
  • 詞元 (token) 是語意含義 (semantic meaning) 的最小單位, 是 LLM 的基本輸入, 它可以是單字, 也可以是子單字 (sub-word).  
  • BERT 使用 Transformer 架構中的編碼器 (忽略解碼器), 擅長迅速處理與理解大量文本, 相較於其他較慢的 LLM 則專注於生成文本, 一次生成一個詞元. BERT 本身不對文本進行分類或摘要提取, 但經常被下游 NLP 任務拿來作為預訓練模型. 
  • T5 使用 Transformer 的編碼器與解碼器, 適合用於既要處理與理解文本, 又要自由輸出文本的應用場景, 且不需要微調即可用於多種 NLP 任務. 
  • Transformer 架構最早是在 2017 年被設計出來, 由編碼器與解碼器組成的序列到序列模型. 解碼器負責將接收到的文本分解為核心組件並轉成向量, 同時利用注意力機制來了解文本的前後脈絡. 解碼器則透過一種改良的注意力機制來預測最符合前後脈絡的下一個詞元, 因此擅長於生成文本. 
  • embedding (內嵌) 是單字, 短句, 或詞元 (token) 在一個多維空間中的數學表示法 (浮點數向量), 可用來捕捉它們的語意含義. embedding 包括 token embedding (詞元內嵌, 編碼語意含義) 與 positional embedding (編碼詞元在句子中的位置).
  • 在 LLM 中, 對齊 (alignment) 是指 LLM 的回應與用戶期望相符的程度, 在訓練循環中加入強化學習 (RL) 是對齊語言模型的方法之一, 其中最熱門的是 RLHF (Reenforcement Learning from Human Feedback), 它是利用相對少量但高品質的人類回饋, 學習調整自己的輸出以提升模型的效果. 
  • 傳統 NLP 會採用諸如停用詞去除 (stop words removal), 詞幹提取 (stemming), 詞形還原 (lemmatization) 與截斷 (truncation) 等前處理技術以簡化輸入特徵, 但這些技術在 LLM 幾乎完全派不上用場, 而且用了還可能會降低模型的效能 (因為前後脈絡資訊可能被移除了). LLM 主要依靠子詞級的 tokenization (切詞) 與 embedding (向量化) 來表示輸入, 讓模型在訓練過程中自行學習語法與語義關係. 不過, 截斷 (truncation) 技術在 LLM 仍然有用到, 只是目的變成控制上下文視窗長度而已. 

第二章 :
  • 使用強大的 LLM 來產生文本的 embedding 是 AI 公司提供的解決方案中用途最廣泛的一種. 文本 embedding 用多維空間裡的向量來表達機器可讀之單字與句子, 這些向量通常是根據它們的前後文來建構的, 可用來捕捉單字與短句的語意值. 這種從文字到向量的對映可以視為一種有意義的雜湊化 (hash with meaning), 通過計算向量之間的距離可以衡量字與字之間的關係 : 如果兩個 embedding 向量的距離很近, 那麼它們的原始字句可能很相似. 

~樂讀中~

2025年9月25日 星期四

momo 購買國際牌無線電話+64GB卡x3

因鄉下老家的無線電話故障, 大概是兩年前在中華電信門市買的, 去日本前一周出現 "不在含蓋範圍" 訊息, 推測是 RF 信號有問題. 今天上 momo 買了國際牌的 KX-TG3712, 此款為可擴音的子母機, 煮菜時有來電可開擴音很實用 : 


另外本周回去要安裝米家攝影機, 需要兩片 TF 卡, 買了金士頓 3 入組 :





合計 2860 元, 用掉 MOMO 幣 300 元, 實付 2560 元. 

2025年9月21日 星期日

2025 年第 37-38 周記事 (日本關西之旅)

上個周日在日本京都, 走到太累了沒時間寫周記, 那就兩周併在一起寫吧! 

這次會走一趟關西, 其實也是趕鴨子上架, 八月的時候水某說她 9/12~9/19 這段期間無病人回診, 可以去日本走走, 我隨口說那就去關西京都大阪吧! 多年以來一直想說有機會去大阪的話, 一定要去尋訪當年出差工作的江坂, 於是日本之行就這樣定下來了. 

我手邊還有很多計畫要進行, 訂機票旅館等等都是水某在處理, 一直到出發前兩周我才開始透過 AI 規劃行程, 前一周才開始借旅遊書看 YT 的攻略, 直到登機前細部行程計畫都還沒完成, 只有大致要去哪些景點的粗略腹案而已, 反正到民宿再計畫明天行程即可 (結果我每天晚上都在研究交通方式而晚睡哈哈).

總結這八天總共走訪了 10 個佛寺 (東福寺, 清水寺, 天龍寺, 大覺寺, 龍安寺, 仁和寺, 金閣寺, 興福寺, 東大寺, 法隆寺), 兩個神社 (八坂神社, 北野天滿宮) 與 2 個景點 (嵐山, 姬路), 全靠巴士, 地鐵, 火車, 與兩隻腳 (還有谷歌地圖), 其中八坂神社到達時已過了五點, 只是進去拍照而已; 法隆寺太晚入寺, 只參觀了正殿, 下次去的話要早上直接搭 JR 去, 巴士無法控制時間. 總之, 自助雖然累, 但可以做到深度旅遊, 把書上讀到的與實地連結, 感覺更深刻. 

今天是母親 11 年周年忌日, 下周回鄉下要到祖祠去巡視看看, 兩個月過去了, 小徑上的草勢必又恣意地長高了, 應該要帶電動打草機去整理一下. 

2025年9月19日 星期五

關西之旅 Day 8 (9/19)

今天結束日本關西之旅返回台灣. 

早上 10 點要退宿, 我九點之前就將內務整理好 (除了棉被外幾乎原狀交還給屋主), 09:30 到附近逛一逛, 來此住了三天都是早出晚歸, 沒有時間好好認識一下周遭. 街角有一個 "北一本通" 社區型商店街, 白天可能要近午才會有店家營業, 每晚 8~9 點回到社區時還燈火通明, 但每天回來都腿軟了, 根本無心去逛.




走回民宿拿行李時聽到巷口傳來哨子聲, 循聲探索發現原來是社區一家幼稚園的繞行活動, 感覺是在模仿祈園祭那樣, 部分小朋友坐在一台小車內, 其他小朋友則在前後排成兩列繞行社區一圈後回幼稚園 : 




近十點關燈鎖門後, 將鑰匙放入門口密碼鎖就告別北加賀屋前往難波, 那邊是地鐵轉乘樞紐, 站內有非常熱鬧的商店街, 本想在此會到下午直接搭南海電鐵去機場, 但中午 11 點離傍晚 6 點半華航櫃台報到還有半天時間, 就把行李寄放在地鐵站靠近往南海電鐵檢票口的寄物櫃 (注意要拍照記下寄物櫃號碼與位置, 免得忘記是寄在哪裡) : 




然後搭御堂筋線到天王寺, 逛逛日本最古老的官立佛寺之一的四天王寺. 到天王寺站出站後谷歌導航顯示似乎要走一段距離, 水某說好像要轉乘谷町線一站到四天王寺前夕陽丘站比較近, 所以又進站去坐谷町線, 出站後有指標往四天王寺, 但其實這樣是從它的後門進寺, 從天王寺站那邊過來是從正門進 :




四天王寺為聖德太子所建 (與法隆寺一樣), 供奉守護佛教的四天王, 象徵著佛教正式融入日本社會. 寺內中心伽藍的五重塔為代表性建築, 遊客可以登上塔內參觀 (這在日本佛寺中非常少有), 塔頂供奉佛陀舍利 :




五重塔對面是用來講經說法的講堂, 裡面牆壁繪有釋迦八相圖, 描繪佛陀從出生到成道與涅槃的八大事跡, 遊客可委託和尚念經祈福迴向, 這也是過去幾天參訪的寺廟所未見的 :





大門口右側是戴斗笠的親鑾上人像 :




參觀完從中之門出來左轉直走即可到 JR 天王寺站公園口, 然後轉乘地鐵回難波站 : 




回到難波後在商店街的田舍吃午餐, 我的是天婦羅定食, 水某的是信濃橋麥麵 : 






吃過飯已近三點, 到寄物櫃取出行李後便前往南海電鐵月台搭車 : 




南海電鐵車窗是圓形的, 好特別. 大約 40 分鐘就到終點站關西空港, 五點多進國際線辦理出境, 然後就是逛免稅店買土產, 準備回台灣啦!

2025年9月18日 星期四

關西之旅 Day 7 (9/18)

今天行程為遊覽姬路城. 早上 08:40 從地鐵四橋線北加賀屋站前往大阪站, 轉乘 09:30 JR 新快速開往姬路的列車 (約 20 分鐘一班), 大約一小時可到達姬路. 從 JR 姬路站出來遠遠就能看見天守閣, 沿著大手前這條大馬路直直走, 盡頭便是姬路城的入口. 




今天陰雨有雲, 步出姬路站就下起小雨, 而早上匆忙出門忘了將傘放回背包, 只好在 7-11 買了一支自動傘. 但走到城門口卻又轉晴了 :




姬路城從不同角度來看都能感受到其巍峨氣勢, 據資料說明, 二戰末期此地遭受盟軍轟炸成為焦土, 但姬路城卻幸運地未受波及 :






終於來到天守閣的入口處 :




天守閣含地下層 (地階) 共七層, 每一層都有這樣的說明牌 :




整個結構的主軸由東大柱與西大柱兩根木柱支撐, 這是一樓的迴廊 : 




每一層都有這種往下丟石塊阻止敵人攻城的 "石落" 窗口 :




格子窗底部也有可開閉的石落 :




二樓主要是存放金銀財寶與武器庫 :




三樓與四樓主要是房間 (內室) :





2025年9月17日 星期三

關西之旅 Day 6 (9/17)

今日行程為奈良之旅, 昨晚做好研究決定買近鐵斑鳩一日券, 但到了地鐵難波站卻一問三不知, 只好算了, 就直接買近鐵奈良線的一般車票. 更糟的是, 早上出門後才發現手機無網路訊號, 數據漫遊時間還沒到啊! 研判是我把 8GB 量用完了, 這時要加買已來不及, 幸好備用手機 iPhone 的 eSIM 卡還能連網, 今天就全靠它了 (但是它不能分享熱點卻是個問題).




近鐵奈良站一出來穿過商店街便是興福寺, 由於部分建築正在進行修整, 所以只遠遠拍了正殿後直接去找寫御朱印之處 :




走出興福寺側門便是奈良公園, 也就是鹿群最多的地方, 35 年前我也在此買了鹿餅餵鹿, 這些鹿不知已過幾代, 早已不是以前之鹿了 : 




從東大寺出來時已 1 點多, 剛好看到一群鹿在狂奔, 其中一隻角上掛了一個女士包包, 想必是餵鹿時被勾住的. 由於在東大寺待久了些, 饗說下午該去奈良南邊的法隆寺, 還是逛一下春日大社後前往較近的藥師寺? 

在東大寺前公車站等車時與奈良市巴士站協助觀光客的小姐閒聊, 她說這時坐巴士去法隆寺雖然趕得上, 但時間有點緊, 我想說都來到奈良了, 不去一趟法隆寺有點可惜, 所以還是決定等下一班 98 號巴士前往法隆寺. 




2025年9月16日 星期二

關西之旅 Day 5 (9/16)

今天是在京都的最後一天, 昨天想起第一天下午去逛的東福寺沒有蓋到御朱印, 早上便先將行李打包好, 08:40 徒步過去等 09:00 辦事人員上班就去蓋朱印. 





回到民宿整理一下便出發前往大阪, 先搭 JR 奈良線到京都站, 再搭 JR 普快車前往大阪, 在大阪站下車後走地下通道至大阪地鐵 (Metro) 西梅田站轉乘四橋線到北加賀屋站, 從 4 號出口出來後發現旁邊剛好有一家牛丼屋, 且也剛好中午 12 點多, 離入住民宿還有一個小時, 便在牛丼屋先吃過午飯 :




吃過飯便拉著行李照導航指示去找尋民宿位址, 今天太陽非常地毒辣, 曬得我滿身大汗. 經過數次 try and error 終於找到民宿了, 原來從北加賀屋小學門口對面巷子走進去就到了 (或 2 號出口直走第二巷口左轉) :




昨天收到民宿主人的訊息時我以為上面的密碼就像京都民宿一樣按門上的電子鎖密碼就可開門, 但這家並無電子鎖, 而是傳統需鑰匙開啟, 但鑰匙在哪? 看見牆上信箱旁有一個號碼鎖, 琢磨了一下才理解原來是要轉到密碼位置開啟這個鎖頭, 鑰匙就放在裡面. 

這是傳統的日式住宅, 玄關進來右邊是廚房 :




櫥櫃裡有精美的和式碗盤, 熱水壺與煮飯的電鍋 :




玄關旁是流理臺與電磁爐 :




中間是客廳 :




後面是衛浴 (淋浴間) 與一台洗脫烘三合一的洗衣機 :






二樓為臥室, 前面是兩個雙人床; 後面是兩人的日式他他米 :





由於天氣酷熱, 休息到三點多才出門到大國町搭御堂筋線至江坂尋訪當年出差近半年的三菱電機, 但 Google Map 似乎無法定位當年地址的正確位置 (畢竟已過了 35 年), 只好拍了一張街角的 Lawson, 那時每天上班經過此店必買麵包+水蜜桃+養樂多當早餐. 




傍晚搭回梅田站在出口的 ELP 百貨逛與吃晚餐, 還上到 6 樓搭摩天輪才回去民宿, 但大阪地鐵實在太複雜, 搞了好久才從蜘蛛網逃脫.