2026年4月11日 星期六

LangChain 學習筆記 : 提示詞模板 (二)

上一篇測試是針對字串提示詞模板類別 PromptTemplate, 主要是用於單一句子的簡單提問, 功能類似於 f 字串, 可快速填充模板參數以得到完整的訊息字串. 本篇旨在測試需要更多上下文語境的對話提示模板類別 ChatPromptTemplate 之用法. 

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


本篇同樣會使用 OpenAI 與 Gemini API 來測試對話提示詞模板用法, 首先匯入所需之金鑰與外掛套件並預先建立模型物件 :  

(myvenv) D:\python\test>python   
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from langchain_openai import ChatOpenAI   
>>> from langchain_google_genai import ChatGoogleGenerativeAI  
>>> from dotenv import dotenv_values   
>>> config=dotenv_values('.env')   
>>> openai_api_key=config.get('OPENAI_API_KEY')   
>>> gemini_api_key=config.get('GEMINI_API_KEY')   
>>> gpt_model=ChatOpenAI(api_key=openai_api_key, model='gpt-3.5-turbo')     
>>> gemini_model=ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.5-flash')   

然後從 langchain_core 匯入 prompts 子模組下的 ChatPromptTemplate 類別 (因為之前只安裝 langchain_core, 沒有安裝大禮包 langchain) :

>>> from langchain_core.prompts import ChatPromptTemplate   

ChatPromptTemplate 類別的核心在於角色扮演, 它不像 PromptTemplate 那樣把所有內容塞成一個大字串, 而是將對話拆解成不同的角色訊息, 在 LangChain 的訊息物件中有三種角色 : 
  • System (系統) : 設定 AI 的人格, 背景, 規則 (例如 "你是一位 Python 專家")
  • Human (使用者) : 你對 AI 的提問
  • AI (助手) : AI 之前回覆過的內容 (用於提供對話脈絡)
呼叫 ChatPromptTemplate 類別的建構式 ChatPromptTemplate() 時, 要把對話內容依照角色拆分, 將其 system, human, 與 ai 三種角色的訊息組成元組串列傳入建構式, 傳回值為一個 ChatPromptTemplate 物件, 例如下面的翻譯對話模板 : 

>>> chat_template=ChatPromptTemplate.from_messages([
    ("system", "你是一位專業的 {style} 翻譯員,擅長將繁體中文翻譯成優雅的 {language}。"), 
    ("human", "請翻譯這段話:{text}"), 
    ]) 

此模板中嵌入了三個模板參數 : style, language, 與 text, 填充此三個參數可以呼叫 ChatPromptTemplate 物件的兩個方法 : 
  • format_message() : 傳入值為個別模板參數, 傳回值為訊息物件串列
  • invoke() : 傳入值為模板參數字典, 傳回值為 ChatPromptValue 物件
不論是哪一種傳回值, 都可以做為參數直接傳給模型物件的 invoke() 方法向 LLM 提交提示詞. 首先來測試使用 format_message() 來填充模板參數, 先準備要翻譯的文本 text : 

>>> text='''趁著午後的陽光灑進窗台,我決定暫時放下手中的電路板與程式碼,給自己泡一杯清茶,享受這難得的靜謐時光。生活不應該只有邏輯與迴圈,還要有隨處可見的驚喜。''' 

然後傳入 style, language, 與 text 參數來填充模板 :

>>> messages=chat_template.format_messages(style="出版業", language="英文", text=text)    
>>> type(messages)   
<class 'list'>   
>>> messages    
[SystemMessage(content='你是一位專業的 出版業 翻譯員,擅長將繁體中文翻譯成優雅的 英文。', additional_kwargs={}, response_metadata={}), HumanMessage(content='請翻譯這段話:趁著午後的陽光灑進窗台,我決定暫時放下手中的電路板與程式碼,給自己泡一杯清茶,享受這難得的靜謐時光。生活不應該只有邏輯與迴圈,還要有隨處可見的驚喜。', additional_kwargs={}, response_metadata={})]

可見 format_messages() 傳回一個訊息物件串列, 裡面有 SystemMessage 與 HumanMessage 等角色的訊息物件 (此對話尚未有 AIMessage 物件), 這樣就可以把填充完的訊息物件串列傳給模型的 invoke() 方法來生成回應了, 例如 GPT : 

>>> response=gpt_model.invoke(messages)   
>>> print(response.content)     
With the afternoon sunlight streaming in through the window, I decided to temporarily set aside the circuit boards and code in my hands, brew myself a cup of clear tea, and savor this rare moment of tranquility. Life shouldn't just be about logic and loops; it should also be filled with unexpected delights at every turn.

下面是串接 Gemini 的回應 :

>>> response=gemini_model.invoke(messages)   
>>> print(response.content)  
以下是幾種翻譯選項,從較為直接到更為優雅的風格:

**選項一 (較為直接,但仍優雅):**

> As the afternoon sun streamed onto the windowsill, I decided to temporarily set aside my circuit board and code. I would brew myself a cup of soothing tea and savor this rare moment of tranquility. Life shouldn't be solely about logic and loops; it should also be filled with readily found surprises.

**選項二 (更為優雅,帶有文學氣息):**

> Bathed in the afternoon sun gracing the windowsill, I chose to momentarily put down my circuit board and code. A cup of fine tea beckoned, promising a rare interlude of serenity. Life, after all, should not be confined to logic and loops, but should also embrace the unexpected delights found at every turn.

**選項三 (簡潔而富有詩意):**

> With the afternoon sun warming the windowsill, I paused from my circuit board and code. A soothing cup of tea, a rare moment of quietude – these were my new companions. For life, I believe, demands more than just logic and loops; it calls for the unexpected joys that lie around every corner.

---

**選擇建議:**

*   如果希望保持原句的結構和語氣,**選項一** 是個不錯的選擇。
*   如果希望加入更多文學色彩和更豐富的詞彙,讓譯文讀起來更具美感,**選項二** 會是最佳選擇。
*   如果偏好更精煉、更具哲思的表達,**選項三** 則能達到這個效果。

我個人最推薦**選項二**,它最能體現「優雅的出版業翻譯」的要求。

接下來改用 ChatPromptTemplate 物件的 invoke() 方法來填充, 這時要把模板參數打包成字典傳給模型物件的 invoke() 方法 : 

>>> messages=chat_template.invoke({
    'style': '科技業', 
    'language': '日文', 
    'text': '這支程式的執行效率非常高。'
    })
>>> type(messages)   
<class 'langchain_core.prompt_values.ChatPromptValue'>   
>>> messages   
ChatPromptValue(messages=[SystemMessage(content='你是一位專業的 科技業 翻譯員,擅長將繁體中文翻譯成優雅的 日文。', additional_kwargs={}, response_metadata={}), HumanMessage(content='請翻譯這段話:這支程式的執行效率非常高。', additional_kwargs={}, response_metadata={})])

可見傳回值是一個 ChatPromptValue 物件, 裡面包裹著 SystemMessage 與 HumanMessage 等角色的訊息物件, 可以直接把這個 ChatPromptValue 物件傳給模型的 invoke() 方法來生成回應, 例如 GPT : 

>>> response=gpt_model.invoke(messages)   
>>> print(response.content)     
このプログラムの実行効率は非常に高いです。

也可串接 Gemini :

>>> response=gemini_model.invoke(messages)   
>>> print(response.content)   
這支程式的執行效率非常高。
**このプログラムは実行効率が非常に高いです。**

或者,稍微更強調性能的話:
**このプログラムの実行効率は非常に優れています。**
*(This program's execution efficiency is excellent.)*

2026年4月10日 星期五

LangChain 學習筆記 : 提示詞模板 (一)

LangChain 的提示詞模板 (Prompt Templates) 功能利用預留變數將靜態文字轉化為動態指令, 可根據不同的傳入參數重複生成結構化的提示詞. LangChain 的 langchain_core.prompts 子模組提供兩種提示詞模板 :
  • 字串提示模板 (PromptTemplate 類別):
    用於單一句子的簡單提問
  • 對話提示模板 (ChatPromptTemplate 類別) :
    用於需要更多上下文語境之完整對話
本篇旨在測試字串提示模板 PromptTemplate 類別的用法. 

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


以下將使用 OpenAI 與 Gemini API 來測試字串提示模板用法, 首先匯入所需之金鑰與外掛套件並預先建立模型物件 :  

(myvenv) D:\python\test>python   
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from langchain_openai import ChatOpenAI   
>>> from langchain_google_genai import ChatGoogleGenerativeAI  
>>> from dotenv import dotenv_values   
>>> config=dotenv_values('.env')   
>>> openai_api_key=config.get('OPENAI_API_KEY')   
>>> gemini_api_key=config.get('GEMINI_API_KEY')   
>>> gpt_model=ChatOpenAI(api_key=openai_api_key, model='gpt-3.5-turbo')     
>>> gemini_model=ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.5-flash')   

由於之前在虛擬目錄下只安裝 langchain_core, 沒有安裝大禮包 langchain; 所以要從 langchain_core 匯入 prompts 子模組下的 PromptTemplate 類別 :

from langchain_core.prompts import PromptTemplate

>>> from langchain_core.prompts import PromptTemplate    

然後呼叫其建構式 PromptTemplate() 並傳入 template (字串) 與 input_variables (串列) 參數建立 PromptTemplate 物件, 其中 input_variables 中列舉了要傳入之模板參數, 例如 : 

prompt_template=PromptTemplate(
    template='用 300 個以內的字說明關於{topic}的知識',
    input_variables=['topic']  
    )

此 PromptTemplate 物件含有一個模板參數 topic. 

>>> prompt_template=PromptTemplate(
...     template='用 300 個以內的字說明關於{topic}的知識',   
...     input_variables=['topic']  
...     )  
>>> type(prompt_template)   
<class 'langchain_core.prompts.prompt.PromptTemplate'>
>>> prompt_template   
PromptTemplate(input_variables=['topic'], input_types={}, partial_variables={}, template='用 300 個以內的字說明關於{topic}的知識')

上面所建立的 PromptTemplate 物件只是一個模板結構而已, 還不能直接做為訊息物件傳給語言模型, 必須呼叫 PromptTemplate 物件的 format() 或 invoke() 方法傳入模板參數進行填充後才行, 這兩個方法的差異如下 : 
  • format() :
    單純的字串填充, 傳入值為單一字串, 傳回值是填充後的字串, 效果類似 f 字串的功能. 
  • invoke() :
    這是 LCEL (LangChain Expression Language) 的標準接口, 是為了配合 LCEL 的鏈式調用而設, 也是推薦的用法. 它的傳入值為模板參數字典, 傳回值為 PromptValue 物件, 它會根據後續接的是純文字模型還是對話模型自動轉成 StringPromptValue 或 ChatPromptValue 物件後傳回. 
總之, 如果是要除錯, 列印提示詞內容, 或串接非 LangChain 的自定義函數等場景, 可以呼叫 format() 以快速得到填充後的字串; 如果是要參與 LangChain 的鏈 (Chain) 運算, 則應該呼叫 invoke(). 

首先用 format() 方法來填充 :

>>> prompt_string=prompt_template.format(topic='疊加態')   
>>> type(prompt_string)    
<class 'str'>  
>>> prompt_string   
'用 300 個以內的字說明關於疊加態的知識'

可見 format() 傳回填充後的訊息字串, 這樣就可以傳給模型物件的 invoke() 讓模型來生成回應了, 例如 OpenAI 的 GPT :

>>> response=gpt_model.invoke(prompt_string)   
>>> print(response.content)  
疊加態是一種量子物理學中的概念,指的是當一個系統同時處於多個可能的狀態時,這些狀態之間可以相互叠加,而不會直接混合在一起。
在疊加態中,系統可以同時存在於不同的狀態中,直到進行測量時才會產生確定的結果。這也解釋了量子超密碼等量子現象的奇異性。
疊加態是量子力學中的一個核心概念,也是量子計算和量子通信等領域的基礎。通過利用疊加態,科學家們可以設計出更快速和更強大的計算機系統,以及更加安全和高效的通訊技術。
總的來說,疊加態是一個深奧而又神秘的概念,它挑戰了我們對現實世界的直覺和理解,也為我們揭示了量子世界的獨特之處。

或者谷歌的 Gemini : 

>>> response=gemini_model.invoke(prompt_string)   
>>> print(response.content)   
疊加態是量子力學中的一個基本概念,指微觀粒子在被測量前,可以同時處於多個可能的狀態。例如,一個電子可以同時向上和向下自旋,或同時在多個位置。只有當我們進行觀測或測量時,疊加態才會「坍縮」,粒子隨機選擇其中一個狀態顯現出來。在未觀測前,它並非處於某個確定的狀態,而是所有可能性的疊加。

其次, 改為呼叫 invoke() 方法, 這時傳入參數是模板參數字典 : 

>>> prompt_value=prompt_template.invoke({'topic': '普郎克常數'})   
>>> type(prompt_value)   
<class 'langchain_core.prompt_values.StringPromptValue'>  
>>> prompt_value   
StringPromptValue(text='用 300 個以內的字說明關於普郎克常數的知識')    

由於模板是純文字模型, 所以傳回值為 StringPromptValue 物件, 可以直接將此 PromptValue 物件傳給模型來生成回應, 例如 GPT :

>>> response=gpt_model.invoke(prompt_value)   
>>> print(response.content)     
普朗克常數是物理學中的一個重要常數,通常用符號h表示。它的數值約為6.626×10^-34 J·s。普朗克常數被廣泛應用於量子力學和粒子物理學中,尤其是在描述微觀世界中微小粒子的運動和行為時。根據普朗克常數,能量和頻率之間存在著一個固定的關係,即E=hf,其中E是能量,h是普朗克常數,f是頻率。普朗克常數還與黑體輻射和光子的能量密度等現象密切相關。因此,普朗克常數在物理學中具有重要的地位,對於我們理解微觀世界的運作方式至關重要。

或者 Gemini 模型 : 

>>> response=gemini_model.invoke(prompt_value)   
>>> print(response.content)  
普朗克常數(h)是一個基本物理常數。它揭示了能量的量子化現象,即能量不是連續的,而是以離散的「量子」形式存在。它將光子的能量E與其頻率ν聯繫起來,公式為E=hν。普朗克常數是量子力學的基石,描述了微觀世界中能量的最小作用量單位。

這就是使用 LangChain 的好處, 不論是串接哪一個 LLM, 呼叫介面都相同. 

AI 應用程式專案 (二) : Youtube 字幕摘要生成器

本篇繼續測試 Oreilly "AI 應用程式開發" 這本書第三章的 App 專案 No.2 : Youtube 影片摘要, 本篇旨在測試如何利用第三方套件抓取 Youtube 影片字幕後丟給 AI 生成影片內容摘要. 此書的範例程式可在 GitHub 下載 :


本專案範例原始碼網址 : 


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



1. 安裝 Youtube 字幕抓取工具 yt-dlp :

我詢問 Gemini 要如何下載 YT 字幕檔, 它推薦用 youtube-transcript-api, 但經測試發現無法下載字幕, 很可能是被 YT 阻擋了, 第二選擇是使用 yt-dlp 套件, 經測試可順利下載字幕檔. 

首先用 pip 安裝此套件 : 

(myvenv) D:\python\test>pip install yt-dlp   
Collecting yt-dlp
  Downloading yt_dlp-2026.3.17-py3-none-any.whl.metadata (182 kB)
Downloading yt_dlp-2026.3.17-py3-none-any.whl (3.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.3/3.3 MB 7.5 MB/s  0:00:00
Installing collected packages: yt-dlp
Successfully installed yt-dlp-2026.3.17

我找了一個含有中英文字幕的 Python 教學短片來測試 :


測試程式如下 (ChatGPT 生成) : 

# get_youtube_transcript_1.py
import sys
import os
import yt_dlp

def get_yt_subtitle_ytdlp(video_id):
    url=f"https://www.youtube.com/watch?v={video_id}"
    # 設定 yt-dlp 參數
    ydl_opts={
        'skip_download': True,        # 不下載影片檔
        'writesubtitles': True,       # 抓取手寫字幕
        'writeautomaticsub': True,    # 如果沒手寫就抓自動生成的
        'subtitleslangs': ['zh-Hant', 'zh-TW', 'en'], # 語言優先順序
        'outtmpl': '%(title)s.%(ext)s',  # 設定輸出檔名主檔名為影片標題
        'quiet': True,
        'no_warnings': True,
        }
    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            print(f"DEBUG: 正在透過 yt-dlp 請求影片 {video_id} 的資訊...")
            info=ydl.extract_info(url, download=False)
            subtitles=info.get('requested_subtitles')
            if subtitles:
                for lang, sub_info in subtitles.items():
                    print(f"✅ 成功找到語言: {lang}")
                # 下載字幕檔
                ydl.download([url])
                # 取得檔名(影片檔名 base)
                base_filename=ydl.prepare_filename(info)
                base_name=os.path.splitext(base_filename)[0]
                # 嘗試找字幕檔
                found_files=[]
                for lang in subtitles.keys():
                    possible_file=f"{base_name}.{lang}.vtt"
                    if os.path.exists(possible_file):
                        found_files.append(possible_file)
                if found_files:
                    for f in found_files:
                        print(f"🎉 字幕已下載: {f}")
                else:
                    print("⚠️ 字幕下載完成,但找不到實際檔案名稱")
            else:
                print("❌ 找不到符合的繁體中文或英文字幕")
    except Exception as e:
        print(f"❌ yt-dlp 抓取失敗: {e}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python script.py [影片ID]")
        sys.exit(1)
    get_yt_subtitle_ytdlp(sys.argv[1])

執行結果如下 : 

(myvenv) D:\python\test>python get_youtube_transcript_2.py OndBl1H1rwM    
DEBUG: 正在透過 yt-dlp 請求影片 OndBl1H1rwM 的資訊...
✅ 成功找到語言: zh-TW
✅ 成功找到語言: en
🎉 字幕已下載: 【Code Gym】Python基礎教學(5) - for迴圈和while迴圈.zh-TW.vtt
🎉 字幕已下載: 【Code Gym】Python基礎教學(5) - for迴圈和while迴圈.en.vtt

開啟檢視繁中字幕檔內容 : 

WEBVTT
Kind: captions
Language: zh-TW

00:00:05.940 --> 00:00:10.400
我們撰寫程式的目的,除了是要建立商業邏輯中判斷的條件

00:00:10.400 --> 00:00:12.960
還需要善用電腦快速運算的能力

00:00:13.140 --> 00:00:16.320
在商業邏輯中執行反覆出現的規則運算

00:00:16.320 --> 00:00:20.820
其中for迴圈和while迴圈就是我們兩個好用的工具

00:00:21.160 --> 00:00:23.680
如果你想要指定程式執行的次數

00:00:23.680 --> 00:00:27.820
或是從容器型態的物件中依序取出裡面的值

00:00:27.820 --> 00:00:31.060
像是我先前介紹過的List, Tuple型態

... (略) ...

00:08:12.700 --> 00:08:17.100
Code Gym頻道主要是分享程式語言教學和電腦網路相關知識

00:08:17.100 --> 00:08:20.900
像是今天影片中所介紹的「for迴圈和while迴圈」

00:08:21.880 --> 00:08:24.240
如果你想要收到最新影片消息

00:08:24.240 --> 00:08:26.080
歡迎訂閱Code Gym頻道

00:08:26.080 --> 00:08:27.020
開小鈴鐺

00:08:27.020 --> 00:08:29.020
我們下次再見,掰掰!

但上面程式有一個缺點, 字幕檔的主檔名使用影片標題, 這可能在之後要用程式開啟檔案時帶來麻煩 (例如標題中有怪碼), 比較好的做法是用影片 ID 當主檔名, 只要修改 yt-dlp 參數中的 'outtmpl' 鍵為 '%(id)s.%(ext)s' 即可 :

'outtmpl': '%(id)s.%(ext)s'

再次執行結果如下 : 

(myvenv) D:\python\test>python get_youtube_transcript_1.py OndBl1H1rwM   
DEBUG: 正在透過 yt-dlp 請求影片 OndBl1H1rwM 的資訊...
✅ 成功找到語言: zh-TW
✅ 成功找到語言: en
🎉 字幕已下載: OndBl1H1rwM.zh-TW.vtt
🎉 字幕已下載: OndBl1H1rwM.en.vtt


2. 串接 OpenAI API 生成影片字幕摘要 :

在上面下載字幕檔程式的基礎上, 將字幕內容經過清理, 去除文字以外的資訊後丟給 GPT 模型生成摘要, 程式碼如下 : 

# get_youtube_transcript_2.py
import sys
import os
import re
import yt_dlp
from openai import OpenAI
from dotenv import dotenv_values

config=dotenv_values('.env') 
openai_api_key=config.get('OPENAI_API_KEY')
client=OpenAI(api_key=openai_api_key)

def clean_vtt(file_path):
    """
    清理 VTT 字幕檔,移除時間軸、標頭與重複的文字區塊,回傳純文字。
    """
    if not os.path.exists(file_path):
        return ""
    with open(file_path, 'r', encoding='utf-8') as f:
        lines=f.readlines()
    clean_text_list=[]
    for line in lines:
        # 移除 WEBVTT 標頭、時間軸 (-->) 與設定行
        if "-->" in line or line.startswith("WEBVTT") or line.startswith("Kind:") or line.startswith("Language:"):
            continue
        # 移除 HTML 標籤 (例如 <c> 標籤)
        line=re.sub(r'<[^>]+>', '', line).strip()
        # 避免加入空白行與重複的行 (VTT 常有重複出現的字幕快照)
        if line and (not clean_text_list or line != clean_text_list[-1]):
            clean_text_list.append(line)
    return "\n".join(clean_text_list)

def ask_gpt(
    messages: list[dict[str, str]],
    model: str='gpt-3.5-turbo'
    ) -> str:
    try:
        reply=client.chat.completions.create(
            model=model, 
            messages=messages
            )
        return reply.choices[0].message.content or ''
    except APIError as e:
        return e.message

def summarizer(text):
    if not text:
        return "無字幕內容可生成摘要。"
    print("\n--- [摘要生成中] ---")
    print(f"(已接收到 {len(text)} 字的字幕內容,準備進行摘要...)")
    # 呼叫 AI 生成摘要
    return ask_gpt([{"role": "user",
                     "content": f"請摘要下列字幕內容 : \n{text}"}])

def get_yt_subtitle_ytdlp(video_id):
    url=f"https://www.youtube.com/watch?v={video_id}"
    # 定義語言優先順序:繁體中文 -> 簡體中文 -> 英文
    lang_priority=['zh-Hant', 'zh-TW', 'zh-Hans', 'zh-CN', 'en']
    ydl_opts={
        'skip_download': True,
        'writesubtitles': True,
        'writeautomaticsub': True,
        'subtitleslangs': lang_priority, 
        'outtmpl': '%(id)s.%(ext)s',  # 強制以影片 ID 為主檔名
        'quiet': True,
        'no_warnings': True,
        }
    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            print(f"DEBUG: 正在透過 yt-dlp 請求影片 {video_id} 的資訊...")
            info=ydl.extract_info(url, download=False)
            subtitles=info.get('requested_subtitles')
            if not subtitles:
                print("❌ 找不到符合要求的字幕。")
                return
            # 下載字幕檔
            ydl.download([url])
            # 依照優先順序尋找已下載的檔案
            selected_file=None
            for lang in lang_priority:
                possible_file=f"{video_id}.{lang}.vtt"
                if os.path.exists(possible_file):
                    selected_file=possible_file
                    print(f"✅ 已選定最優語言字幕: {lang} ({selected_file})")
                    break
            if selected_file:
                # 1. 清理字幕
                print(f"🧹 正在清理字幕格式...")
                cleaned_content=clean_vtt(selected_file)
                # 2. 生成摘要
                summary_result=summarizer(cleaned_content)
                print("\n[摘要結果]:")
                print(summary_result)
                # 可選:實驗完成後刪除暫存的 vtt 檔
                # os.remove(selected_file)
            else:
                print("⚠️ 檔案下載完成,但讀取時找不到檔案。")
    except Exception as e:
        print(f"❌ 執行過程中發生錯誤: {e}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python script.py [影片ID]")
        sys.exit(1)
    get_yt_subtitle_ytdlp(sys.argv[1])

執行結果如下 : 

(myvenv) D:\python\test>python get_youtube_transcript_2.py OndBl1H1rwM   
DEBUG: 正在透過 yt-dlp 請求影片 OndBl1H1rwM 的資訊...
✅ 已選定最優語言字幕: zh-TW (OndBl1H1rwM.zh-TW.vtt)
🧹 正在清理字幕格式...

--- [摘要生成中] ---
(已接收到 2339 字的字幕內容,準備進行摘要...)

[摘要結果]:
本文介紹了在撰寫程式中使用for迴圈和while迴圈的基本概念和用法。for迴圈主要用於從容器型態中依序取出值,可以指定程式執行的次數或範圍,使用range()函式可以簡化處理。在for迴圈中,可以使用break和continue來控制迴圈的流程。而while迴圈則是根據條件式的判斷結果來決定是否執行程式區塊,可以用來進行猜數字等互動式遊戲。最後,介紹了如何匯入Python模組,在學習完本文後可以在程式編輯軟體上實際練習程式碼。

2026年4月9日 星期四

AI 應用程式專案 (一) : 新聞稿生成器

我的 OpenAI API 帳戶自去年再次儲值 5 美元後, 只做了少許生圖測試便晾在一邊, 轉眼已過了大半年, 為了避免一年使用期限到期沒用完被沒收, 打算拿 Oreilly "AI 應用程式開發" 這本書第三章的六個 App 專案來消耗掉. 

測試環境使用最近為了 LangChain 測試而建立的乾淨虛擬環境, 參考 :


此書的範例程式可在 GitHub 下載 :


本篇旨在測試第三章中的專案 1 : 打造一個新聞稿生成器, 書中範例原始碼參考 : 


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


此專案的目的是建立一個 AI 應用程式, 可以指定文章長度, 語氣與風格來生成新聞稿. 書中範例程式使用了 typing.List 來進行類型提示 (Type Hinting), 例如 facts: List[str] 是告訴閱讀程式碼的人或 IDE : facts 這個參數應該是一個串列, 且這個串列裡面的每個元素都應該是字串. 不過, 這種用法在 Python 3.9 之後已經內建到 list 中了, 新寫法是 facts: list[str], 所以我將原始範例程式碼改寫為如下 :

# news_generator_1.py
from openai import OpenAI
from dotenv import dotenv_values

config=dotenv_values('.env') 
openai_api_key=config.get('OPENAI_API_KEY')
client=OpenAI(api_key=openai_api_key)

def ask_gpt(
    messages: list[dict[str, str]],
    model: str = 'gpt-3.5-turbo'
    ) -> str:
    try:
        reply=client.chat.completions.create(
            model=model, 
            messages=messages
            )
        return reply.choices[0].message.content or ''
    except APIError as e:
        return e.message

prompt_role='''You are an assistant for journalists. 
Your task is to write articles, based on the FACTS that are given to you. 
You should respect the instructions: the TONE, the LENGTH, and the STYLE'''
# 你是記者的助理. 
# 你的任務是根據所提供的事實 (FACTS) 來撰寫一篇新聞稿.
# 文章需符合指定的語氣 (TONE), 長度 (LENGTH), 與風格 (STYLE). 

def assist_journalist(
    facts: list[str], 
    tone: str, 
    length_words: int, 
    style: str
    ) -> str:
    """根據傳入的事實、語氣, 長度與風格生成新聞稿"""
    facts_str=", ".join(facts)
    prompt=f'{prompt_role}\nFACTS: {facts_str}\nTONE: {tone}\nLENGTH: {length_words} words\nSTYLE: {style}'
    return ask_gpt([{"role": "user", "content": prompt}])

# 執行範例
if __name__ == "__main__":
    result=assist_journalist(
        facts=[
            '2026年東京櫻花預計在3月底滿開', 
            '上野公園是著名的賞櫻勝地,有超過1000棵櫻花樹', 
            '傍晚會有點燈活動,可以欣賞浪漫的夜櫻',
            '請使用繁體中文撰寫'  
            ],
        tone='浪漫且活潑', 
        length_words=300, 
        style='旅遊部落格文章'
        )
    print(result)

此處兩個函式 assist_journalist() 與 ask_gpt() 的傳入參數都使用了類型提示語法以增加程式碼可讀性. 例如 ask_gpt() 中的 messages: list[dict[str, str]] 意思是 :
  • messages 是一個串列, 裡面的每個元素都是字典.
  • 字典的鍵與值都是字串, 例如 {"role": "user", "content": "hello"}
而 model: str = 'gpt-3.5-turbo' 表示 model 是一個字串且有預設值. -> str 表示傳回值類型為字串. 

執行結果如下 :

(myvenv) D:\python\test>python news_generator_1.py   
東京櫻花季即將來臨!2026年的櫻花季預計在3月底盛大開放,其中上野公園絕對是不能錯過的賞櫻勝地之一。這個充滿浪漫氛圍的公園擁有超過1000棵櫻花樹,每年都吸引著無數遊客前來欣賞這片粉紅色的花海。

除了白天的賞櫻之外,上野公園還有一項令人神往的活動,那就是傍晚的點燈活動。當夜幕降臨時,整個公園被點亮,營造出一種浪漫的氛圍,讓遊客們可以在夜幕下欣賞到迷人的夜櫻。這種別具一格的賞櫻體驗絕對讓人流連忘返。

在櫻花季期間,上野公園也會舉辦不同的活動和表演,讓遊客可以感受到濃濃的日本文化氛圍。無論是品嚐傳統的日本料理、參加傳統音樂表演還是購買精美的手工藝品,都能讓遊客們深入體驗到日本的文化魅力。

此外,上野公園周邊還擁有眾多商店和小吃攤位,供遊客選擇,讓您可以在欣賞櫻花的同時品嚐道地的日本美食和購買紀念品。不僅如此,公園周邊還有許多著名的博物館和寺廟,讓遊客可以一次過體驗到日本的豐富文化遺產。

總括而言,2024年的東京櫻花季將會是一場難忘的賞櫻之旅。無論是白天還是夜晚,在上野公園中都能感受到浪漫氛圍,讓您與摯愛共度美好時光。趕快計劃您的行程,一起來感受日本春天的魅力吧!

下面是 Gemini 版本的程式碼 :

# news_generator_2.py
from google import genai
from google.genai.errors import APIError
from dotenv import dotenv_values

config=dotenv_values('.env') 
gemini_api_key=config.get('GEMINI_API_KEY')
client=genai.Client(api_key=gemini_api_key)

def ask_gemini(messages: str, model: str='gemini-2.5-flash') -> str:
    try:
        reply=client.models.generate_content(
            model=model, 
            contents=messages
            )
        return reply.text or ''
    except APIError as e:
        return e.message

prompt_role='''You are an assistant for journalists. 
Your task is to write articles, based on the FACTS that are given to you. 
You should respect the instructions: the TONE, the LENGTH, and the STYLE'''
# 你是記者的助理. 
# 你的任務是根據所提供的事實 (FACTS) 來撰寫一篇新聞稿.
# 文章需符合指定的語氣 (TONE), 長度 (LENGTH), 與風格 (STYLE). 

def assist_journalist(
    facts: list[str], 
    tone: str, 
    length_words: int, 
    style: str
    ) -> str:
    """根據傳入的事實、語氣, 長度與風格生成新聞稿"""
    facts_str=", ".join(facts)
    prompt=f'{prompt_role}\nFACTS: {facts_str}\nTONE: {tone}\nLENGTH: {length_words} words\nSTYLE: {style}'
    # Gemini 可直接接受字串作為 contents, 傳入串列可能會解析失敗
    return ask_gemini(prompt)

# 執行範例
if __name__ == "__main__":
    result=assist_journalist(
        facts=[
            '2026年東京櫻花預計在3月底滿開', 
            '上野公園是著名的賞櫻勝地,有超過1000棵櫻花樹', 
            '傍晚會有點燈活動,可以欣賞浪漫的夜櫻',
            '請使用繁體中文撰寫'  # 提示模型使用特定語言
            ],
        tone='浪漫且活潑', 
        length_words=300, 
        style='旅遊部落格文章'
        )
    print(result)

此 Gemini 版與上面 OpenAI 版主要不同處有二 :
  • 錯誤處理須匯入 google.genai.errors.APIError 類別.
  • Gemini SDK 可以接受字串作為 contents, 所以在 ask_gemini() 中傳入參數 messages 是字串, 而不是 OpenAI 中的串列 [{"role": "user", "content": prompt}], 否則 Gemini 的 SDK 可能會解析失敗 (視於版本相容性而定), 同時 assist_journalist() 內呼叫 ask_gemini() 也是直接傳 prompt 字串. 
注意, 此處使用新版 Gemini SDK : google-genai 而非舊版的 google-generativeai, 參考下面這篇底下的補充 :


執行結果如下 :

(myvenv) D:\python\test>python news_generator_2.py
## 2026東京櫻花前線速報!上野公園千株櫻花與夢幻夜櫻等你來!

嘿,各位櫻花迷們!是不是已經開始期待下一個粉紅色的春天了呢?告訴你一個振奮人心的好消息!2026年東京的櫻花季,預計將在**3月底**達到最美、最浪漫的滿開!準備好迎接這場不容錯過的春日盛典了嗎?

想抓住這份稍縱即逝的絕美風景,怎能錯過東京最經典、最有活力的賞櫻勝地——**上野公園**呢?這裡可是名符其實的「櫻花海」!園內種植了**超過1000棵**櫻花樹,想像一下,漫步在粉白色的花海隧道下,微風輕拂,花瓣如雪般飄落,光是想像就讓人心醉神迷。白天,公園裡總是充滿著歡聲笑語,野餐、散步、拍照,每一處都是生機勃勃的春日氣息。

但別以為太陽下山就結束了!上野公園的魅力在傍晚時分才真正達到高潮!入夜後,園內精心設計的**點燈活動**會將這些嬌嫩的櫻花裝扮得如夢似幻。一盞盞溫暖的光芒,輕柔地映照著粉白花朵,將白天的活潑氣氛轉化為一片極致浪漫的**夜櫻仙境**。和心愛的人手牽手,在微光中欣賞這份獨特的景色,絕對會是2026年春日最難忘的記憶,為你的東京之旅增添無限美好的浪漫色彩!

所以,親愛的朋友們,2026年3月底,趕快把你的行事曆空下來吧!東京上野公園的千株櫻花與夢幻夜櫻正等著你,一起來感受這場浪漫又活潑的春日盛典!別再猶豫了,現在就開始規劃你的東京櫻花之旅吧!

2026年4月7日 星期二

好書 : 歸剛 Vibe Coding

今天在博客來找到這本 Vibe coding 的電子書 :


我用 HyRead 查詢市圖未進此書, 但台灣雲端書庫有, 借期 14 天, 由於篇幅不大, 我今天就看完了, 摘要心得如下 :
  • 創作時只要專注在 "我要完成甚麼", 而不是去糾結 "我要學習甚麼", 碰到問題才查才問, 這樣完成率就會很高. 這種狀態下, 沒有刻意要學, 卻學得超快. 
  • 對企業來說, 員工永遠都是可有可無的開銷. 
  • 創業的原理很簡單 : 發現需求 -> 發揮想像力與觀察力 -> 勇敢去實踐
  • 創業=解決問題+創造價值+實際賺錢.
  • 上班族賣的是時間, 創業者賣的是點子變出來的資產.
  • AI 時代的成功不再是靠背景, 學歷, 資源, 而是靠有想法+會動手. 
  • Vibe coding 的核心觀念 : 先創造感覺, 再修細節>
  • Vibe coding 步驟 :
    • 快速生成原型 : 利用 AI 工具+現成框架 (Next.js, Tailwind, Supabase, ...)
    • 即時發布 (Vercel 等)
    • 社群平台 (Discord, Threads, ...) 回饋驅動迭代
  • Vibe coding 是從寫得正確轉向為先做出感覺. 市場變化很快, 與其寫規格書, 不如先丟作品上去看市場反應. 
  • 不論是做網站還是 App, 開發任何產品前首先要問 : 誰是使用者? 不要去想 "他們需要甚麼功能" (feature-to-be-shipped), 而是要想 "他們想完成甚麼事" (job-to-be-done). 總之, 不要急著做, 先把使用人的問題搞懂. 
  • Rig : 一套能把靈感迅速變成現實的工具組合 (AI 工具 + 開發框架 + 雲端佈署). 
  • 開發框架技術堆疊 :
    • Next.js : 最快速的 React 框架
    • Tailwind : 直接用 class 寫樣式
    • Supabase / PlanetScal : 資料庫即服務
    • Bun / Vite : 快速的 Javascript 框架
  • 網頁工具技術堆疊 :
    • Figma : 網頁 UI 外觀
    • Wenflow / Proto.io : 互動網頁工具
    • Vercel : 一鍵完成佈署
  • Vibe coding 的關鍵不是要懂很多技術, 而是要選對工具與問對問題. 
  • 向 AI 提問時最大的忌諱是一次丟出一個又大又模糊的問題, 否則它會亂猜你的需求, 應該要這樣問 : "我想做一個 xxx, 裡面有 xxx, 先問我問題跟我討論". 在動手開發之前, 應該先進行討論, 而不是直接行動. 問 AI 有甚麼問題要問你其實就是讓 AI 幫你找出完成此專案需要考慮的事項, 讓你得到一連串切中核心的問題. 
  • 與 AI 溝通的要訣 :
    • 可在專案目標後面加上這段 : "請針對我提出的目標和方向提出最詳細的問題, 不要當好好先生, 別甚麼都同意, 要質疑我選的方向與決定, 永遠不要自行假設", 可避免 AI 亂編內容, 並能主動問你問題以釐清細節.
    • 請 AI 一次做一個步驟, 否則它會一股腦兒把全部解決方案爆出來, 如果方向錯誤要浪費較多時間導正. 可加上這段提示 : "請一步一步來, 不要一次全部給我". 
  • 隨著來回討論越長, AI 的反應會越慢效率變差, 可用下列提示詞來做一個總結與繼承動作 : "請整理目前我們所建立的重要上下文, 包含所有決策, 定義, 專案目標, 技術設置, 程式結構, 使用工具, 命名規則, 文字語氣, 以及任何對此專案重要的資訊, 請整理得清楚明瞭, 方便我複製貼上". 然後複製下來, 開啟一個新對話貼上, 同時貼上專案資料夾的專案結構樹 (用 tree 指令). 然後加上這句 "請在開始對話前, 告訴我你想要我貼上哪些檔案的內容?", 將第一個檔案內容貼給它時, 加上這句 "這是第一個檔案, 只要告訴我你收到了即可, 不用進行分析,  然後告訴我你要看的下一個檔案, 等全部貼完再分析."  (我覺得這個做法很不錯)

2026年4月6日 星期一

Hahow 購買黑木太太日語課

最近在 fb 看到黑木太太日語課在募資, 今天是 88 折優惠最後一天, 想說退休前的這幾年會很常去日本玩, 最好把日語練好一點, 就購買了此課程 :





最近開車都在聽 Yumi 老師的日文課, 聽久了不知不覺就背起來了. 

2026 年第 13 周記事

本周清明連假只上四天班, 下周一補假, 周五天氣酷熱, 按計畫完成芭樂樹下雜草清除, 並到墓園巡視給花瓶添水. 周六下雨待在家, 雖然完成兩篇 LangChain 測試, 但原本要在車庫安裝監視器的計畫, 卻因為沉迷 fb 上的雍正王朝解說短片而沒做. 週日與周一終於抽空動手組裝置物櫃與衣櫥, 準備整理庫房的衣服與三樓的書籍. 

第二季的學習重點是 Vibe coding, 首先要把購買的線上課程抽空看完, 目前還是使用 Gemini CLI, 下一季再考慮切入 Claude Code. 其次要複習一月以來的幾個很棒的內訓課程, 但上課時我幾乎都沒時間實作, 光聽不練其實沒有用, 所以我上課必定錄影 (但錄到現在卻幾乎沒時間複習). 

我最近實在花太多時間在滑臉書了, 演算法總是丟一些吸引我眼球的天文學與歷史劇害我難以脫身. 進入四月後較無俗事 (下次事六月的端午節拜拜), 應該要靜下心來好好追進度了. 另外, 路旁水圳蓮霧樹上的照明燈打算改裝, 想製作一個鳥屋來安裝監視器 + 照明燈. 




菜園旁的舊豬舍牆上則想要裝上草莓的垂直栽培架 :




兩周前安安表弟幫我用舊電腦弄了一台 NAS, 目前只有一顆 500GB 硬碟, 但一直沒時間去設定路由器, 今天檢查發現居然當機了, 等有時間再來玩看看. 

2026年4月5日 星期日

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

在前一篇測試中, 我使用 venv 建立一個虛擬環境, 並於其中安裝 langchain-google-genai 與 langchain-openai 這兩個外掛套件來分別串接 Gemini 與 OpenAI API, 目前 Gemini 免費帳戶可用 gemini-2.5-flash 與 gemini-2.5-flash-lite 等快捷與低成本模型; OpenAI 可使用經典的 gpt-3.5-turbo 或較新的 gpt-4o-mini 模型 (需付費購買 API key). 

本篇旨在測試如何查詢有哪些模型可用, 這會用到 OpenAI 與 Gemini 的原生 API : openai 與 google_genai, 這兩個套件在安裝 langchain_openai 與 langchain_google_genai 時都會作為底層依賴套件被自動安裝. 

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



1. 查詢 OpenAI 模型 : 

查詢 OpenAI 模型列表是透過 OpenAI 物件的 models 屬性的 list() 方法 : 

# list_openai_models.py
from openai import OpenAI
from dotenv import dotenv_values

# 讀取 .env 中的 OPENAI_API_KEY
config=dotenv_values('.env')
client=OpenAI(api_key=config.get('OPENAI_API'))

print("--- OpenAI 目前可用模型清單 ---")
try:
    models=client.models.list()
    for m in models:
        # 過濾掉一些老舊或非對話的模型,只看主流的
        if "gpt" in m.id or "o3" in m.id or "o5" in m.id:
            print(f"模型 ID: {m.id}")
except Exception as e:
    print(f"查詢失敗:{e}")

結果如下 :

(myvenv) D:\python\test>python list_openai_models.py
--- OpenAI 目前可用模型清單 ---
模型 ID: gpt-4-0613
模型 ID: gpt-4
模型 ID: gpt-3.5-turbo
模型 ID: gpt-5.4-mini
模型 ID: gpt-5.4
模型 ID: gpt-5.4-nano-2026-03-17
模型 ID: gpt-5.4-nano
模型 ID: gpt-5.4-mini-2026-03-17
模型 ID: gpt-3.5-turbo-instruct
模型 ID: gpt-3.5-turbo-instruct-0914
模型 ID: gpt-3.5-turbo-1106
模型 ID: gpt-3.5-turbo-0125
模型 ID: gpt-4-turbo
模型 ID: gpt-4-turbo-2024-04-09
模型 ID: gpt-4o
模型 ID: gpt-4o-2024-05-13
模型 ID: gpt-4o-mini-2024-07-18
模型 ID: gpt-4o-mini
模型 ID: gpt-4o-2024-08-06
模型 ID: gpt-4o-audio-preview
模型 ID: gpt-4o-realtime-preview
模型 ID: gpt-4o-realtime-preview-2024-12-17
模型 ID: gpt-4o-audio-preview-2024-12-17
模型 ID: gpt-4o-mini-realtime-preview-2024-12-17
模型 ID: gpt-4o-mini-audio-preview-2024-12-17
模型 ID: gpt-4o-mini-realtime-preview
模型 ID: gpt-4o-mini-audio-preview
模型 ID: o3-mini
模型 ID: o3-mini-2025-01-31
模型 ID: gpt-4o-2024-11-20
模型 ID: gpt-4o-mini-search-preview-2025-03-11
模型 ID: gpt-4o-mini-search-preview
模型 ID: gpt-4o-transcribe
模型 ID: gpt-4o-mini-transcribe
模型 ID: gpt-4o-mini-tts
模型 ID: o3-2025-04-16
模型 ID: o3
模型 ID: gpt-4.1-2025-04-14
模型 ID: gpt-4.1
模型 ID: gpt-4.1-mini-2025-04-14
模型 ID: gpt-4.1-mini
模型 ID: gpt-4.1-nano-2025-04-14
模型 ID: gpt-4.1-nano
模型 ID: gpt-image-1
模型 ID: gpt-4o-realtime-preview-2025-06-03
模型 ID: gpt-4o-audio-preview-2025-06-03
模型 ID: gpt-4o-transcribe-diarize
模型 ID: gpt-5-chat-latest
模型 ID: gpt-5-2025-08-07
模型 ID: gpt-5
模型 ID: gpt-5-mini-2025-08-07
模型 ID: gpt-5-mini
模型 ID: gpt-5-nano-2025-08-07
模型 ID: gpt-5-nano
模型 ID: gpt-audio-2025-08-28
模型 ID: gpt-realtime
模型 ID: gpt-realtime-2025-08-28
模型 ID: gpt-audio
模型 ID: gpt-5-codex
模型 ID: gpt-image-1-mini
模型 ID: gpt-5-pro-2025-10-06
模型 ID: gpt-5-pro
模型 ID: gpt-audio-mini
模型 ID: gpt-audio-mini-2025-10-06
模型 ID: gpt-5-search-api
模型 ID: gpt-realtime-mini
模型 ID: gpt-realtime-mini-2025-10-06
模型 ID: gpt-5-search-api-2025-10-14
模型 ID: gpt-5.1-chat-latest
模型 ID: gpt-5.1-2025-11-13
模型 ID: gpt-5.1
模型 ID: gpt-5.1-codex
模型 ID: gpt-5.1-codex-mini
模型 ID: gpt-5.1-codex-max
模型 ID: gpt-image-1.5
模型 ID: gpt-5.2-2025-12-11
模型 ID: gpt-5.2
模型 ID: gpt-5.2-pro-2025-12-11
模型 ID: gpt-5.2-pro
模型 ID: gpt-5.2-chat-latest
模型 ID: gpt-4o-mini-transcribe-2025-12-15
模型 ID: gpt-4o-mini-transcribe-2025-03-20
模型 ID: gpt-4o-mini-tts-2025-03-20
模型 ID: gpt-4o-mini-tts-2025-12-15
模型 ID: gpt-realtime-mini-2025-12-15
模型 ID: gpt-audio-mini-2025-12-15
模型 ID: chatgpt-image-latest
模型 ID: gpt-5.2-codex
模型 ID: gpt-5.3-codex
模型 ID: gpt-realtime-1.5
模型 ID: gpt-audio-1.5
模型 ID: gpt-4o-search-preview
模型 ID: gpt-4o-search-preview-2025-03-11
模型 ID: gpt-5.3-chat-latest
模型 ID: gpt-5.4-2026-03-05
模型 ID: gpt-5.4-pro
模型 ID: gpt-5.4-pro-2026-03-05
模型 ID: gpt-3.5-turbo-16k


2. 查詢 Gemini 模型 : 

查詢 Gemini 模型列表是透過 genai 物件的 models 屬性的 list() 方法, 注意, 新版 API 的判斷欄位改為 supported_actions (舊版為 supported_generation_methods) : 

# list_gemini_models.py
from google import genai
from dotenv import dotenv_values

# 1. 讀取 .env
config=dotenv_values('.env')
api_key=config.get('GEMINI_API_KEY')

# 2. 初始化 Client (新版 API 不再用 genai.configure)
client=genai.Client(api_key=api_key)
print("--- 可用的 Google AI 模型如下: ---")

try:
    # 3. 使用 client.models.list() 查詢
    for m in client.models.list():
        # 新版 API 的判斷欄位為 supported_actions
        if 'generateContent' in m.supported_actions:
            print(f'模型 ID: {m.name:}')
            
except Exception as e:
    print(f"查詢出錯:{e}")

執行結果 : 

(myvenv) D:\python\test>python list_gemini_models.py  
--- 可用的 Google AI 模型如下: ---
模型 ID: models/gemini-2.5-flash
模型 ID: models/gemini-2.5-pro
模型 ID: models/gemini-2.0-flash
模型 ID: models/gemini-2.0-flash-001
模型 ID: models/gemini-2.0-flash-lite-001
模型 ID: models/gemini-2.0-flash-lite
模型 ID: models/gemini-2.5-flash-preview-tts
模型 ID: models/gemini-2.5-pro-preview-tts
模型 ID: models/gemma-3-1b-it
模型 ID: models/gemma-3-4b-it
模型 ID: models/gemma-3-12b-it
模型 ID: models/gemma-3-27b-it
模型 ID: models/gemma-3n-e4b-it
模型 ID: models/gemma-3n-e2b-it
模型 ID: models/gemma-4-26b-a4b-it
模型 ID: models/gemma-4-31b-it
模型 ID: models/gemini-flash-latest
模型 ID: models/gemini-flash-lite-latest
模型 ID: models/gemini-pro-latest
模型 ID: models/gemini-2.5-flash-lite
模型 ID: models/gemini-2.5-flash-image
模型 ID: models/gemini-3-pro-preview
模型 ID: models/gemini-3-flash-preview
模型 ID: models/gemini-3.1-pro-preview
模型 ID: models/gemini-3.1-pro-preview-customtools
模型 ID: models/gemini-3.1-flash-lite-preview
模型 ID: models/gemini-3-pro-image-preview
模型 ID: models/nano-banana-pro-preview
模型 ID: models/gemini-3.1-flash-image-preview
模型 ID: models/lyria-3-clip-preview
模型 ID: models/lyria-3-pro-preview
模型 ID: models/gemini-robotics-er-1.5-preview
模型 ID: models/gemini-2.5-computer-use-preview-10-2025
模型 ID: models/deep-research-pro-preview-12-2025

也可以不直接使用原生 API (google_genai 套件), 而是透過 LangChain 調用底層 Client 物件 :

# list_gemini_models_2.py
from langchain_google_genai import ChatGoogleGenerativeAI
from dotenv import dotenv_values

config=dotenv_values('.env')
api_key=config.get('GEMINI_API_KEY')

# 初始化 LangChain 模型物件
chat_model=ChatGoogleGenerativeAI(model='gemini-2.5-flash', api_key=api_key)
print("--- 透過 LangChain 的底層 Client 查詢模型 ---")

try:
    # 直接存取 LangChain 內部的 client 物件 (這就是 google-genai 的實體)
    # 注意:這屬於「私有屬性」存取,通常用於調錯或特殊查詢
    client=chat_model.client
    for m in client.models.list():
        if 'generateContent' in m.supported_actions:
            print(f"可用模型: {m.name}")
            
except Exception as e:
    print(f"查詢失敗:{e}")

執行結果與上面直接使用原生 API 相同 : 

(myvenv) D:\python\test>python list_gemini_models_2.py    
--- 透過 LangChain 的底層 Client 查詢模型 ---
可用模型: models/gemini-2.5-flash
可用模型: models/gemini-2.5-pro
可用模型: models/gemini-2.0-flash
可用模型: models/gemini-2.0-flash-001
可用模型: models/gemini-2.0-flash-lite-001
可用模型: models/gemini-2.0-flash-lite
可用模型: models/gemini-2.5-flash-preview-tts
可用模型: models/gemini-2.5-pro-preview-tts
可用模型: models/gemma-3-1b-it
可用模型: models/gemma-3-4b-it
可用模型: models/gemma-3-12b-it
可用模型: models/gemma-3-27b-it
可用模型: models/gemma-3n-e4b-it
可用模型: models/gemma-3n-e2b-it
可用模型: models/gemma-4-26b-a4b-it
可用模型: models/gemma-4-31b-it
可用模型: models/gemini-flash-latest
可用模型: models/gemini-flash-lite-latest
可用模型: models/gemini-pro-latest
可用模型: models/gemini-2.5-flash-lite
可用模型: models/gemini-2.5-flash-image
可用模型: models/gemini-3-pro-preview
可用模型: models/gemini-3-flash-preview
可用模型: models/gemini-3.1-pro-preview
可用模型: models/gemini-3.1-pro-preview-customtools
可用模型: models/gemini-3.1-flash-lite-preview
可用模型: models/gemini-3-pro-image-preview
可用模型: models/nano-banana-pro-preview
可用模型: models/gemini-3.1-flash-image-preview
可用模型: models/lyria-3-clip-preview
可用模型: models/lyria-3-pro-preview
可用模型: models/gemini-robotics-er-1.5-preview
可用模型: models/gemini-2.5-computer-use-preview-10-2025
可用模型: models/deep-research-pro-preview-12-2025

注意, 由於 Google 與 OpenAI 兩家公司在 SDK 設計邏輯上的根本差異, 使得 LangChain 在整合這兩者時採取了不同策略, Gemini 可以直接透過 .client 取得模型清單, 而 OpenAI 則不行, 因此查詢 OpenAI 的模型清單必須用底層 SDK 套件 openai. 

2026年4月4日 星期六

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

3/21 從日本旅行回來後感覺有很多東西要學, 但卻不知從哪一個著手. 昨天整理書桌看到案上的 LangChain 書籍, 想說那就從 LangChain 開始吧! 記得 LangChain 就是去年九月第一次關西之旅回來後開始學習的.

由於我的 LG Gram 筆電的 Thonny Python 執行環境太亂了, 存在嚴重的版本衝突問題, 所以在重新出發之前, 先建立一個專門用來串接 GenAI 的乾淨虛擬環境較好. 我之前曾使用 poetry 來建立與管理虛擬環境, 但本篇則是要使用傳統且熟悉的 venv. 

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


開啟命令提示字元視窗建立一個名為 myvenv 虛擬環境 :

D:\python\test>python -m venv myvenv  
D:\python\test>myvenv\Scripts\activate   
(myvenv) D:\python\test>  

用 pip list 檢視可知只有一個 pip 套件而已 :

(myvenv) D:\python\test>pip list
Package Version
------- -------
pip     23.2.1

[notice] A new release of pip is available: 23.2.1 -> 26.0.1
[notice] To update, run: python.exe -m pip install --upgrade pip

更新 pip 版本 : 

(myvenv) D:\python\test>python.exe -m pip install --upgrade pip   
Requirement already satisfied: pip in d:\python\test\myvenv\lib\site-packages (23.2.1)
Collecting pip
  Obtaining dependency information for pip from https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl.metadata
  Downloading pip-26.0.1-py3-none-any.whl.metadata (4.7 kB)
Downloading pip-26.0.1-py3-none-any.whl (1.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 2.1 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 23.2.1
    Uninstalling pip-23.2.1:
      Successfully uninstalled pip-23.2.1
Successfully installed pip-26.0.1

(myvenv) D:\python\test>

用下列指令來安裝透過 LangChain 串接 Gemini 與 OpenAI 所需的套件 : 

pip install langchain-google-genai langchain-openai python-dotenv  

其中 langchain-google-genai 與 langchain-openai 分別是串接 Gemini 與 OpenAI 的外掛, 安裝它們時會自動安裝 LangChain 的核心引擎 langchain-core, 這是 LangChain 的心臟, 包含所有的基礎邏輯 (Base Message, Chat Prompt 等), 安裝外掛時會自動附帶. 注意, 不需要安裝 langchain 套件, 這是一個塞滿了各種實驗性功能和舊代碼的大禮包, 非常龐雜. 

(myvenv) D:\python\test>pip install langchain-google-genai langchain-openai python-dotenv    
Collecting langchain-google-genai
  Downloading langchain_google_genai-4.2.1-py3-none-any.whl.metadata (2.7 kB)
Collecting langchain-openai
  Downloading langchain_openai-1.1.12-py3-none-any.whl.metadata (3.1 kB)
Collecting python-dotenv
  Downloading python_dotenv-1.2.2-py3-none-any.whl.metadata (27 kB)
Collecting filetype<2.0.0,>=1.2.0 (from langchain-google-genai)
  Using cached filetype-1.2.0-py2.py3-none-any.whl.metadata (6.5 kB)
Collecting google-genai<2.0.0,>=1.56.0 (from langchain-google-genai)
  Using cached google_genai-1.70.0-py3-none-any.whl.metadata (52 kB)
Collecting langchain-core<2.0.0,>=1.2.5 (from langchain-google-genai)
  Downloading langchain_core-1.2.26-py3-none-any.whl.metadata (4.4 kB)
Collecting pydantic<3.0.0,>=2.0.0 (from langchain-google-genai)
  Downloading pydantic-2.12.5-py3-none-any.whl.metadata (90 kB)
Collecting anyio<5.0.0,>=4.8.0 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Using cached anyio-4.13.0-py3-none-any.whl.metadata (4.5 kB)
Collecting google-auth<3.0.0,>=2.48.1 (from google-auth[requests]<3.0.0,>=2.48.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Using cached google_auth-2.49.1-py3-none-any.whl.metadata (6.2 kB)
Collecting httpx<1.0.0,>=0.28.1 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Using cached httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
Collecting requests<3.0.0,>=2.28.1 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading requests-2.33.1-py3-none-any.whl.metadata (4.8 kB)
Collecting tenacity<9.2.0,>=8.2.3 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading tenacity-9.1.4-py3-none-any.whl.metadata (1.2 kB)
Collecting websockets<17.0,>=13.0.0 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading websockets-16.0-cp312-cp312-win_amd64.whl.metadata (7.0 kB)
Collecting typing-extensions<5.0.0,>=4.14.0 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Collecting distro<2,>=1.7.0 (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Using cached distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)
Collecting sniffio (from google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)
Collecting idna>=2.8 (from anyio<5.0.0,>=4.8.0->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading idna-3.11-py3-none-any.whl.metadata (8.4 kB)
Collecting pyasn1-modules>=0.2.1 (from google-auth<3.0.0,>=2.48.1->google-auth[requests]<3.0.0,>=2.48.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading pyasn1_modules-0.4.2-py3-none-any.whl.metadata (3.5 kB)
Collecting cryptography>=38.0.3 (from google-auth<3.0.0,>=2.48.1->google-auth[requests]<3.0.0,>=2.48.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading cryptography-46.0.6-cp311-abi3-win_amd64.whl.metadata (5.7 kB)
Collecting certifi (from httpx<1.0.0,>=0.28.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading certifi-2026.2.25-py3-none-any.whl.metadata (2.5 kB)
Collecting httpcore==1.* (from httpx<1.0.0,>=0.28.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)
Collecting h11>=0.16 (from httpcore==1.*->httpx<1.0.0,>=0.28.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
Collecting jsonpatch<2.0.0,>=1.33.0 (from langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Using cached jsonpatch-1.33-py2.py3-none-any.whl.metadata (3.0 kB)
Collecting langsmith<1.0.0,>=0.3.45 (from langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading langsmith-0.7.25-py3-none-any.whl.metadata (15 kB)
Collecting packaging>=23.2.0 (from langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading packaging-26.0-py3-none-any.whl.metadata (3.3 kB)
Collecting pyyaml<7.0.0,>=5.3.0 (from langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading pyyaml-6.0.3-cp312-cp312-win_amd64.whl.metadata (2.4 kB)
Collecting uuid-utils<1.0,>=0.12.0 (from langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading uuid_utils-0.14.1-cp39-abi3-win_amd64.whl.metadata (4.9 kB)
Collecting jsonpointer>=1.9 (from jsonpatch<2.0.0,>=1.33.0->langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading jsonpointer-3.1.1-py3-none-any.whl.metadata (2.4 kB)
Collecting orjson>=3.9.14 (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading orjson-3.11.8-cp312-cp312-win_amd64.whl.metadata (43 kB)
Collecting requests-toolbelt>=1.0.0 (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Using cached requests_toolbelt-1.0.0-py2.py3-none-any.whl.metadata (14 kB)
Collecting xxhash>=3.0.0 (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading xxhash-3.6.0-cp312-cp312-win_amd64.whl.metadata (13 kB)
Collecting zstandard>=0.23.0 (from langsmith<1.0.0,>=0.3.45->langchain-core<2.0.0,>=1.2.5->langchain-google-genai)
  Downloading zstandard-0.25.0-cp312-cp312-win_amd64.whl.metadata (3.3 kB)
Collecting annotated-types>=0.6.0 (from pydantic<3.0.0,>=2.0.0->langchain-google-genai)
  Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.41.5 (from pydantic<3.0.0,>=2.0.0->langchain-google-genai)
  Downloading pydantic_core-2.41.5-cp312-cp312-win_amd64.whl.metadata (7.4 kB)
Collecting typing-inspection>=0.4.2 (from pydantic<3.0.0,>=2.0.0->langchain-google-genai)
  Downloading typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)
Collecting charset_normalizer<4,>=2 (from requests<3.0.0,>=2.28.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl.metadata (41 kB)
Collecting urllib3<3,>=1.26 (from requests<3.0.0,>=2.28.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading urllib3-2.6.3-py3-none-any.whl.metadata (6.9 kB)
Collecting openai<3.0.0,>=2.26.0 (from langchain-openai)
  Downloading openai-2.30.0-py3-none-any.whl.metadata (29 kB)
Collecting tiktoken<1.0.0,>=0.7.0 (from langchain-openai)
  Downloading tiktoken-0.12.0-cp312-cp312-win_amd64.whl.metadata (6.9 kB)
Collecting jiter<1,>=0.10.0 (from openai<3.0.0,>=2.26.0->langchain-openai)
  Downloading jiter-0.13.0-cp312-cp312-win_amd64.whl.metadata (5.3 kB)
Collecting tqdm>4 (from openai<3.0.0,>=2.26.0->langchain-openai)
  Downloading tqdm-4.67.3-py3-none-any.whl.metadata (57 kB)
Collecting regex>=2022.1.18 (from tiktoken<1.0.0,>=0.7.0->langchain-openai)
  Downloading regex-2026.4.4-cp312-cp312-win_amd64.whl.metadata (41 kB)
Collecting cffi>=2.0.0 (from cryptography>=38.0.3->google-auth<3.0.0,>=2.48.1->google-auth[requests]<3.0.0,>=2.48.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading cffi-2.0.0-cp312-cp312-win_amd64.whl.metadata (2.6 kB)
Collecting pycparser (from cffi>=2.0.0->cryptography>=38.0.3->google-auth<3.0.0,>=2.48.1->google-auth[requests]<3.0.0,>=2.48.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading pycparser-3.0-py3-none-any.whl.metadata (8.2 kB)
Collecting pyasn1<0.7.0,>=0.6.1 (from pyasn1-modules>=0.2.1->google-auth<3.0.0,>=2.48.1->google-auth[requests]<3.0.0,>=2.48.1->google-genai<2.0.0,>=1.56.0->langchain-google-genai)
  Downloading pyasn1-0.6.3-py3-none-any.whl.metadata (8.4 kB)
Collecting colorama (from tqdm>4->openai<3.0.0,>=2.26.0->langchain-openai)
  Using cached colorama-0.4.6-py2.py3-none-any.whl.metadata (17 kB)
Downloading langchain_google_genai-4.2.1-py3-none-any.whl (66 kB)
Using cached filetype-1.2.0-py2.py3-none-any.whl (19 kB)
Using cached google_genai-1.70.0-py3-none-any.whl (760 kB)
Using cached anyio-4.13.0-py3-none-any.whl (114 kB)
Using cached distro-1.9.0-py3-none-any.whl (20 kB)
Using cached google_auth-2.49.1-py3-none-any.whl (240 kB)
Using cached httpx-0.28.1-py3-none-any.whl (73 kB)
Downloading httpcore-1.0.9-py3-none-any.whl (78 kB)
Downloading langchain_core-1.2.26-py3-none-any.whl (508 kB)
Using cached jsonpatch-1.33-py2.py3-none-any.whl (12 kB)
Downloading langsmith-0.7.25-py3-none-any.whl (359 kB)
Downloading pydantic-2.12.5-py3-none-any.whl (463 kB)
Downloading pydantic_core-2.41.5-cp312-cp312-win_amd64.whl (2.0 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 4.2 MB/s  0:00:00
Downloading pyyaml-6.0.3-cp312-cp312-win_amd64.whl (154 kB)
Downloading requests-2.33.1-py3-none-any.whl (64 kB)
Downloading charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl (159 kB)
Downloading idna-3.11-py3-none-any.whl (71 kB)
Downloading tenacity-9.1.4-py3-none-any.whl (28 kB)
Using cached typing_extensions-4.15.0-py3-none-any.whl (44 kB)
Downloading urllib3-2.6.3-py3-none-any.whl (131 kB)
Downloading uuid_utils-0.14.1-cp39-abi3-win_amd64.whl (187 kB)
Downloading websockets-16.0-cp312-cp312-win_amd64.whl (178 kB)
Downloading langchain_openai-1.1.12-py3-none-any.whl (88 kB)
Downloading openai-2.30.0-py3-none-any.whl (1.1 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.1/1.1 MB 3.1 MB/s  0:00:00
Downloading jiter-0.13.0-cp312-cp312-win_amd64.whl (205 kB)
Downloading tiktoken-0.12.0-cp312-cp312-win_amd64.whl (878 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 878.7/878.7 kB 4.4 MB/s  0:00:00
Downloading python_dotenv-1.2.2-py3-none-any.whl (22 kB)
Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)
Downloading certifi-2026.2.25-py3-none-any.whl (153 kB)
Downloading cryptography-46.0.6-cp311-abi3-win_amd64.whl (3.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.5/3.5 MB 5.0 MB/s  0:00:00
Downloading cffi-2.0.0-cp312-cp312-win_amd64.whl (183 kB)
Downloading h11-0.16.0-py3-none-any.whl (37 kB)
Downloading jsonpointer-3.1.1-py3-none-any.whl (7.7 kB)
Downloading orjson-3.11.8-cp312-cp312-win_amd64.whl (127 kB)
Downloading packaging-26.0-py3-none-any.whl (74 kB)
Downloading pyasn1_modules-0.4.2-py3-none-any.whl (181 kB)
Downloading pyasn1-0.6.3-py3-none-any.whl (83 kB)
Downloading regex-2026.4.4-cp312-cp312-win_amd64.whl (277 kB)
Using cached requests_toolbelt-1.0.0-py2.py3-none-any.whl (54 kB)
Downloading tqdm-4.67.3-py3-none-any.whl (78 kB)
Downloading typing_inspection-0.4.2-py3-none-any.whl (14 kB)
Downloading xxhash-3.6.0-cp312-cp312-win_amd64.whl (31 kB)
Downloading zstandard-0.25.0-cp312-cp312-win_amd64.whl (506 kB)
Using cached colorama-0.4.6-py2.py3-none-any.whl (25 kB)
Downloading pycparser-3.0-py3-none-any.whl (48 kB)
Downloading sniffio-1.3.1-py3-none-any.whl (10 kB)
Installing collected packages: filetype, zstandard, xxhash, websockets, uuid-utils, urllib3, typing-extensions, tenacity, sniffio, regex, pyyaml, python-dotenv, pycparser, pyasn1, packaging, orjson, jsonpointer, jiter, idna, h11, distro, colorama, charset_normalizer, certifi, annotated-types, typing-inspection, tqdm, requests, pydantic-core, pyasn1-modules, jsonpatch, httpcore, cffi, anyio, tiktoken, requests-toolbelt, pydantic, httpx, cryptography, openai, langsmith, google-auth, langchain-core, langchain-openai, google-genai, langchain-google-genai
Successfully installed annotated-types-0.7.0 anyio-4.13.0 certifi-2026.2.25 cffi-2.0.0 charset_normalizer-3.4.7 colorama-0.4.6 cryptography-46.0.6 distro-1.9.0 filetype-1.2.0 google-auth-2.49.1 google-genai-1.70.0 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 idna-3.11 jiter-0.13.0 jsonpatch-1.33 jsonpointer-3.1.1 langchain-core-1.2.26 langchain-google-genai-4.2.1 langchain-openai-1.1.12 langsmith-0.7.25 openai-2.30.0 orjson-3.11.8 packaging-26.0 pyasn1-0.6.3 pyasn1-modules-0.4.2 pycparser-3.0 pydantic-2.12.5 pydantic-core-2.41.5 python-dotenv-1.2.2 pyyaml-6.0.3 regex-2026.4.4 requests-2.33.1 requests-toolbelt-1.0.0 sniffio-1.3.1 tenacity-9.1.4 tiktoken-0.12.0 tqdm-4.67.3 typing-extensions-4.15.0 typing-inspection-0.4.2 urllib3-2.6.3 uuid-utils-0.14.1 websockets-16.0 xxhash-3.6.0 zstandard-0.25.0

檢視目前 pip 套件 :

(myvenv) D:\python\test>pip list
Package                Version
---------------------- ---------
annotated-types        0.7.0
anyio                  4.13.0
certifi                2026.2.25
cffi                   2.0.0
charset-normalizer     3.4.7
colorama               0.4.6
cryptography           46.0.6
distro                 1.9.0
filetype               1.2.0
google-auth            2.49.1
google-genai           1.70.0
h11                    0.16.0
httpcore               1.0.9
httpx                  0.28.1
idna                   3.11
jiter                  0.13.0
jsonpatch              1.33
jsonpointer            3.1.1
langchain-core         1.2.26
langchain-google-genai 4.2.1
langchain-openai       1.1.12
langsmith              0.7.25
openai                 2.30.0
orjson                 3.11.8
packaging              26.0
pip                    26.0.1
pyasn1                 0.6.3
pyasn1_modules         0.4.2
pycparser              3.0
pydantic               2.12.5
pydantic_core          2.41.5
python-dotenv          1.2.2
PyYAML                 6.0.3
regex                  2026.4.4
requests               2.33.1
requests-toolbelt      1.0.0
sniffio                1.3.1
tenacity               9.1.4
tiktoken               0.12.0
tqdm                   4.67.3
typing_extensions      4.15.0
typing-inspection      0.4.2
urllib3                2.6.3
uuid_utils             0.14.1
websockets             16.0
xxhash                 3.6.0
zstandard              0.25.0

可見除了核心套件 langchain-core 外, 還有串接 OpenAI 與 Gemini 的 langchain-openai 與 langchain-google-genai 套件; 此外還會安裝底層的 SDK 依賴套件 google-genai 與 openai. 如果想用原生 SDK 串接 API 就可以使用這兩個套件. 其中 google-genai 是谷歌 2024 年底發布的新版 SDK, 用來取代舊版的 google-generativeai (已於 2025 年進入維護模式), 用法參考下面這篇的補充說明 :


先來串接 OpenAI API :

(myvenv) D:\python\test>python   
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from dotenv import dotenv_values    
>>> config=dotenv_values('.env')   
>>> openai_api_key=config.get('OPENAI_API')   
>>> from langchain_openai import ChatOpenAI   
>>> chat_model=ChatOpenAI(api_key=openai_api_key, model='gpt-3.5-turbo')   
>>> response=chat_model.invoke('你是誰?')    
>>> response.content     
'我是一個機器人助手,可以回答你的問題和提供幫助。有什麼我可以為你做的嗎?'

更換為 gpt-4o-mini 模型 :

>>> chat_model=ChatOpenAI(api_key=openai_api_key, model='gpt-4o-mini')   
>>> response=chat_model.invoke('你是誰?')   
>>> response.content     
'我是一个人工智能助手,旨在回答你的问题和提供帮助。如果你有任何疑问或需要的信息,请随时告诉我!'

下面是串接 Gemini :

>>> from dotenv import dotenv_values   
>>> config=dotenv_values('.env')   
>>> gemini_api_key=config.get('GEMINI_API_KEY')   
>>> from langchain_google_genai import ChatGoogleGenerativeAI   
>>> chat_model=ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.5-flash')    
>>> response=chat_model.invoke('你是誰?')     
>>> response.content   
'我是一个大型语言模型,由 Google 训练。'

更換模型 :

>>> chat_model=ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.5-flash-lite')   
>>> response=chat_model.invoke('你是誰?')   
>>> response.content   
'我是一個大型語言模型,由 Google 訓練。'