跳到主要內容

AG Day 15 ToolNode:工具節點與錯誤處理

AG Day 15 ToolNode:工具節點與錯誤處理

執行需求:CPU+API key。在昨天 AG Day 14(原文連結)中,我們用 LangGraph 內建的 create_react_agent 快速組出一個可以自主推理、呼叫工具的 ReAct 代理,讓 research-agent 第一次擁有「思考、行動、觀察」的完整迴圈。但 create_react_agent 是一個高度封裝的預製函式,內部到底怎麼執行工具、工具出錯時發生什麼事,昨天我們並沒有拆開來看。今天我們要把這個黑盒子打開,改成手動組裝 StateGraph,直接使用 LangGraph 的 ToolNode,並且針對研究助理最常遇到的失敗情境(網路逾時、參數錯誤、工具丟例外)設計一套明確的錯誤處理策略。完成今天之後,你會清楚知道工具呼叫失敗時,代理看到的到底是什麼內容,以及你能在哪些地方介入。

引言

ReAct 代理的核心迴圈很單純:模型看歷史訊息、決定要不要呼叫工具、呼叫後把結果塞回歷史、再讓模型看一次。這個迴圈裡「呼叫工具」這一步,在 LangGraph 裡是由一個叫 ToolNode 的內建節點負責。它的工作是讀取最後一則 AIMessage 裡的 tool_calls 欄位,逐一找到對應的工具函式、把參數傳進去執行、再把回傳值包成 ToolMessage 加回狀態。聽起來理所當然,但工程上真正棘手的地方在於:工具是我們自己寫的程式碼,隨時可能因為網路斷線、外部服務逾時、或呼叫方傳了奇怪的參數而丟出例外。如果一個工具的例外直接讓整條圖執行中斷,使用者只會看到一串 Python traceback,完全不知道代理到底想做什麼、卡在哪一步。

更麻煩的是,很多工程師的直覺反應是「把每個工具函式都包一層 try/except,出錯就回傳一個字串」。這樣做在單一工具上沒問題,但當工具數量變多(研究助理未來會有網路搜尋、擷取網頁、寫入知識庫、查詢向量庫等十幾個工具),到處重複同一套 try/except 樣板,會讓程式碼難以維護,而且很容易漏掉某個工具忘記包。LangGraph 的 ToolNode 提供了一個內建的 handle_tool_errors 參數,讓我們可以在節點層級集中定義「工具出錯時要怎麼辦」,而不必在每個工具函式裡各自處理。今天我們會實際比較「工具內部自己處理例外」和「交給 ToolNode 統一處理」這兩種寫法的差異,並選出一套適合 research-agent 長期維護的組合。

原理與觀念

ToolNode 如何把 AIMessage 轉成 ToolMessage

當模型判斷需要呼叫工具時,它回傳的 AIMessage 會帶有一個 tool_calls 串列,每個元素包含工具名稱、參數(已經是解析好的字典)與一組 id。ToolNode 在建立時會接收一份工具函式清單,內部建立「名稱到函式」的對照表。執行時,它會逐一走過 tool_calls,依名稱找到函式、用參數呼叫、把回傳值(通常是字串)包成一則 ToolMessage,並把 tool_call_id 設成呼叫端給的 id,讓模型在下一輪能正確對應「這個結果是回答哪一個呼叫」。如果代理在同一輪要求並行呼叫多個工具,ToolNode 預設會依序(或視版本以非同步併發)執行完所有呼叫,再把所有 ToolMessage 一次性附加回狀態,模型才會看到完整的一輪結果。

錯誤發生時,ToolNode 預設會做什麼

如果工具函式在執行過程中丟出例外,ToolNode 預設的行為是把例外訊息捕捉起來,包成一則內容為錯誤說明的 ToolMessage,並標記一個表示錯誤的欄位,讓圖繼續往下走而不是整個崩潰。這個預設行為的優點是「代理不會被工具的意外中斷」,模型下一輪看到錯誤訊息後,往往會嘗試換一種參數或換一個工具重試。但缺點也很明顯:如果錯誤是我們自己程式碼裡的臭蟲(例如型別寫錯),這種「吞掉例外繼續跑」的行為會讓除錯變得困難,因為終端機不會顯示完整的 traceback。handle_tool_errors 參數就是用來調整這個行為的開關:可以設成 True(使用預設吞例外邏輯)、設成 False(例外直接往外拋,讓開發環境看到完整堆疊)、或傳入一個自訂函式/訊息字串,決定要回給模型看到的錯誤內容長什麼樣子。

研究助理需要區分「可重試的錯誤」與「該讓人知道的錯誤」

對 research-agent 來說,工具失敗大致分成兩類。第一類是暫時性錯誤,例如搜尋 API 逾時、外部網站暫時無法連線,這類錯誤適合讓模型知道「這次沒拿到結果,可以換個查詢字串或稍後再試」,屬於 ToolNode 吞例外並回傳說明訊息的情境。第二類是結構性錯誤,例如工具參數驗證失敗(模型傳了不存在的欄位)、或是資料庫寫入失敗,這類錯誤代表程式邏輯或提示詞設計有問題,應該記錄到日誌讓開發者事後檢查,而不只是讓模型自己瞎猜。我們今天的實作會把這兩類錯誤分開處理:暫時性錯誤交給 tenacity 在工具內部重試幾次,重試仍失敗才轉成結構化的錯誤字串回傳;結構性錯誤則除了回傳錯誤訊息給模型,也同步寫進 events 資料表,方便之後追查。

完整實作

我們先在 src/research_agent/tools.py 定義兩個示範工具:search_web(帶重試與逾時控制的網路搜尋,若沒有 TAVILY_API_KEY 會自動退回離線模擬結果)與 fetch_url(用 httpx 抓取網頁原始文字,示範參數驗證失敗的情境)。這兩個工具會在後面的檢索篇章持續擴充,今天先把錯誤處理的骨架做對。

# research-agent/src/research_agent/tools.py
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from research_agent.config import load_settings

class ToolInputError(ValueError):
    """工具參數驗證失敗時拋出,屬於結構性錯誤,不重試。"""


class ToolTransientError(RuntimeError):
    """外部服務暫時不可用時拋出,屬於可重試的暫時性錯誤。"""


@retry(
    retry=retry_if_exception_type(ToolTransientError),
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=8),
    reraise=True,
)
def search_web(query: str, max_results: int = 5) -> str:
    """搜尋網路,回傳條列式摘要文字。無金鑰時回傳離線模擬結果。"""
    if not query or not query.strip():
        raise ToolInputError("query 不可為空字串")

    settings = load_settings()
    if settings.is_dry_run or not settings.tavily_api_key:
        return (
            f"[離線模擬結果] 針對「{query}」的模擬搜尋摘要:"
            "此為 --dry-run 模式產生的示範內容,非真實網路查詢結果。"
        )

    try:
        response = httpx.get(
            "https://api.tavily.com/search",
            params={"query": query, "max_results": max_results},
            headers={"Authorization": f"Bearer {settings.tavily_api_key}"},
            timeout=8.0,
        )
        response.raise_for_status()
    except httpx.TimeoutException as exc:
        raise ToolTransientError(f"搜尋逾時:{query}") from exc
    except httpx.HTTPStatusError as exc:
        if exc.response.status_code >= 500:
            raise ToolTransientError(f"搜尋服務暫時錯誤:{exc.response.status_code}") from exc
        raise ToolInputError(f"搜尋請求被拒絕:{exc.response.status_code}") from exc

    payload = response.json()
    results = payload.get("results", [])[:max_results]
    lines = [f"- {item.get('title', '無標題')}:{item.get('url', '')}" for item in results]
    return "\n".join(lines) if lines else "查無相關結果。"

接著是 fetch_url,故意示範一個明確的參數驗證錯誤,讓我們等一下能對照 handle_tool_errors 的兩種行為:

# research-agent/src/research_agent/tools.py(續)
def fetch_url(url: str) -> str:
    """抓取指定網址的原始 HTML 文字,僅接受 http/https 開頭的網址。"""
    if not url.startswith(("http://", "https://")):
        raise ToolInputError(f"url 必須以 http:// 或 https:// 開頭,收到:{url}")

    try:
        response = httpx.get(url, timeout=10.0, follow_redirects=True)
        response.raise_for_status()
    except httpx.TimeoutException as exc:
        raise ToolTransientError(f"抓取逾時:{url}") from exc

    return response.text[:4000]

接下來在 src/research_agent/graph.py 手動組裝圖,取代昨天的 create_react_agent。這裡的重點是自訂一個 error_handler 函式傳給 ToolNode,讓結構性錯誤(ToolInputError)與暫時性錯誤(重試耗盡後的 ToolTransientError)回傳不同措辭的訊息,並把結構性錯誤額外寫進日誌:

# research-agent/src/research_agent/graph.py
import logging
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from research_agent.tools import search_web, fetch_url, ToolInputError, ToolTransientError
from research_agent.llm import call_model

logger = logging.getLogger("research_agent")

TOOLS = [search_web, fetch_url]


def handle_tool_error(exc: Exception) -> str:
    """集中決定:工具丟例外時,要回給模型看到的文字內容。"""
    if isinstance(exc, ToolInputError):
        logger.warning("工具參數錯誤(結構性):%s", exc)
        return f"工具呼叫參數有誤,請修正後再試一次:{exc}"
    if isinstance(exc, ToolTransientError):
        logger.info("工具暫時性失敗,已重試但仍失敗:%s", exc)
        return f"外部服務暫時無法使用,建議稍後再試或換個查詢方式:{exc}"
    logger.error("工具發生未預期例外:%s", exc, exc_info=True)
    return f"工具執行發生未預期錯誤,已記錄供開發者排查:{type(exc).__name__}"


tool_node = ToolNode(TOOLS, handle_tool_errors=handle_tool_error)


def build_graph():
    graph = StateGraph(MessagesState)
    graph.add_node("agent", call_model)
    graph.add_node("tools", tool_node)
    graph.add_edge(START, "agent")
    graph.add_conditional_edges("agent", tools_condition, {"tools": "tools", END: END})
    graph.add_edge("tools", "agent")
    return graph.compile()

這裡的 tools_condition 是 LangGraph 內建的判斷函式:檢查最後一則訊息有沒有 tool_calls,有就導向 "tools" 節點,沒有就導向 END,省去我們自己寫條件邊的樣板程式碼(條件邊的細節在 AG Day 13 已經拆解過)。要注意 handle_tool_errors 接受的簽章依 LangGraph 版本可能是「接受例外物件」或「接受例外並回傳字串」,實際參數型別請以你所安裝版本的官方文件為準;今天示範的寫法是把判斷邏輯集中成一個函式,方便對照與測試。

最後寫一段驗證腳本,刻意觸發一次結構性錯誤(網址格式不對)與一次正常呼叫,確認兩種訊息都符合預期:

# research-agent/verify_toolnode.py
from research_agent.graph import build_graph
from langchain_core.messages import HumanMessage

app = build_graph()

result = app.invoke({
    "messages": [HumanMessage(content="幫我搜尋一下 LangGraph 的 ToolNode 用法")]
})
for msg in result["messages"]:
    print(type(msg).__name__, ":", getattr(msg, "content", "")[:80])

離線模式下的示範輸出(實際文字會依模型回應略有不同):

HumanMessage : 幫我搜尋一下 LangGraph 的 ToolNode 用法
AIMessage : (模型決定呼叫 search_web,內容可能為空或說明呼叫意圖)
ToolMessage : [離線模擬結果] 針對「LangGraph 的 ToolNode 用法」的模擬搜尋摘要:此為 --dry-run 模式產生的示範內容...
AIMessage : 根據搜尋結果(示範資料),ToolNode 的用法大致是...

光看整合測試還不夠,我們也應該替 handle_tool_error 這個決定「錯誤要怎麼被看見」的核心函式,補上單元測試,確保結構性錯誤與暫時性錯誤真的會走到不同的分支、產出不同措辭的訊息:

# research-agent/tests/test_tool_error_handling.py
import logging
from research_agent.graph import handle_tool_error
from research_agent.tools import ToolInputError, ToolTransientError


def test_structural_error_mentions_參數(caplog):
    with caplog.at_level(logging.WARNING):
        message = handle_tool_error(ToolInputError("url 必須以 http 開頭"))
    assert "參數" in message
    assert "工具參數錯誤" in caplog.text


def test_transient_error_suggests_retry_later(caplog):
    with caplog.at_level(logging.INFO):
        message = handle_tool_error(ToolTransientError("搜尋逾時"))
    assert "稍後再試" in message


def test_unknown_exception_is_logged_as_error(caplog):
    with caplog.at_level(logging.ERROR):
        message = handle_tool_error(KeyError("不存在的欄位"))
    assert "未預期錯誤" in message
    assert "KeyError" in message

這三個測試分別對應三種例外路徑,跑 pytest tests/test_tool_error_handling.py -v 應該全數通過。把錯誤處理邏輯獨立成一個可以單獨測試的函式(而不是散落在 ToolNode 的行內 lambda),是今天實作裡另一個值得留意的設計:它讓我們能在不啟動整個代理、也不需要任何 API 金鑰的情況下,驗證錯誤訊息的措辭是否符合預期。

最後,我們在 src/research_agent/cli.py 加一個最小的命令列進入點,把 build_graph() 接上使用者輸入,同時示範離線模式與正式模式如何切換:

# research-agent/src/research_agent/cli.py
import argparse
from langchain_core.messages import HumanMessage
from research_agent.graph import build_graph
from research_agent.config import load_settings


def main() -> None:
    parser = argparse.ArgumentParser(description="research-agent 命令列入口")
    parser.add_argument("query", help="要研究的問題")
    parser.add_argument("--dry-run", action="store_true", help="強制使用離線模擬模式")
    args = parser.parse_args()

    settings = load_settings(force_dry_run=args.dry_run)
    mode = "離線模擬" if settings.is_dry_run else "正式連線"
    print(f"[research-agent] 執行模式:{mode},模型:{settings.model_name}")

    app = build_graph()
    result = app.invoke({"messages": [HumanMessage(content=args.query)]})
    final = result["messages"][-1]
    print(f"\n最終回覆:\n{final.content}")


if __name__ == "__main__":
    main()

常見錯誤與踩雷

第一個常見錯誤是工具函式回傳了非字串的物件(例如直接回傳一個 dict 或 DataFrame)。ToolMessage 的內容欄位預期是字串,如果工具回傳其他型別,有些版本的 LangGraph 會嘗試自動序列化、有些則會在建構 ToolMessage 時直接拋出型別錯誤。穩妥的做法是在工具函式的最後一行明確 str() 或用 json.dumps 轉成字串,不要依賴框架幫你隱式轉換。

第二個是把 handle_tool_errors 設成 True(沿用預設吞例外行為)卻忘了自己另外加日誌,結果所有工具錯誤都被靜靜吞掉,終端機只看到模型很客氣地說「抱歉,這個查詢沒有結果」,完全不知道背後其實是程式碼拋了例外。開發階段建議至少在自己的錯誤處理函式裡加一行 logger.warning 或 logger.error,把原始例外訊息留下痕跡。

第三個是把「重試」邏輯放錯位置:如果把 tenacity 的 @retry 裝飾器套在整個 ToolNode 外層,而不是套在個別工具函式上,重試時會連同「呼叫哪個工具、傳什麼參數」這一整輪決策都重新跑一次,可能導致模型換了完全不同的工具或參數,不是原本想要的「同一個呼叫再試一次」。今天的實作把 @retry 直接放在 search_web 函式上,確保重試的目標是同一次工具呼叫,而不是整個代理迴圈。

第四個雷是 tool_call_id 對應錯亂:如果自己手刻 ToolMessage(而不是交給 ToolNode 建構),很容易忘記把 tool_call_id 設成對應的呼叫 id,導致模型在下一輪無法判斷哪個結果對應哪個呼叫,進而重複呼叫同一個工具。除非有特殊需求,一般情況下都建議讓 ToolNode 統一處理這個對應關係,不要自己手動組裝。

效能與實務提醒

當模型在同一輪要求並行呼叫多個工具(例如同時搜尋三個不同關鍵字)時,ToolNode 對每個獨立工具呼叫是各自執行、互不影響的:其中一個失敗不會拖累其他呼叫成功回傳結果,這是它相對於手刻迴圈的一個實用優勢。設計工具時,盡量讓每個工具呼叫都是獨立且冪等的,避免工具之間有隱性的執行順序依賴,否則平行執行時容易產生難以重現的錯誤。

逾時設定也值得留意:我們在 search_web 與 fetch_url 都明確設了 timeout,因為預設不設逾時的網路呼叫,在外部服務異常時可能讓整條代理迴圈卡住數十秒甚至更久,使用者體感會非常糟。建議把逾時秒數設成環境變數可調整的參數,正式環境依實際外部服務的延遲分布調整,而不是寫死一個猜測值。

另外,重試次數與退避策略要考慮呼叫端的總步數上限。我們在 AG Day 2(原文連結)建立的 RESEARCH_AGENT_MAX_STEPS 是限制代理總共能思考幾輪,如果單一工具內部又疊加了三次重試、每次退避數秒,一次工具呼叫可能就吃掉使用者好幾秒到十幾秒的等待時間。建議把工具內部重試次數控制在 2 到 3 次,並把最大等待時間設在個位數秒,避免使用者長時間看著沒有任何回饋的畫面。

小結

今天我們把昨天用 create_react_agent 隱藏起來的工具執行機制攤開來看:ToolNode 如何從 AIMessage.tool_calls 找到對應函式、執行、包成 ToolMessage,以及 handle_tool_errors 如何讓我們集中控制錯誤要怎麼回報給模型。我們把工具失敗分成「可重試的暫時性錯誤」與「該被記錄的結構性錯誤」兩類,並用 tenacity 搭配自訂例外類別實作出對應的處理邏輯,讓 research-agent 在面對外部服務不穩定時更有韌性。

今天新增的關鍵詞:工具節點(ToolNode)——負責執行工具呼叫並把結果包裝回狀態的內建節點;條件邊(tools_condition)——判斷是否需要進入工具節點的內建路由函式;暫時性錯誤與結構性錯誤——決定錯誤該重試還是該被記錄的分類方式。

結語

把工具執行的錯誤處理做扎實之後,research-agent 已經能在單輪對話裡穩定地呼叫工具、面對失敗也不會整個崩潰。但目前每一次呼叫 app.invoke(...) 都是全新的對話,代理完全不記得前一次研究到哪裡了。對一個要花好幾輪才能完成的研究任務來說,這是個明顯的缺口。

明天,我們會進入「AG Day 16 記憶:checkpointer 與 thread」,介紹 LangGraph 的檢查點機制,讓代理能夠把每一輪的狀態存下來,透過 thread_id 區分不同研究任務,並且能在中斷後從上次停下的地方繼續,而不必從頭再問一次。

延伸資源

  • LangGraph 官方文件:ToolNode 與 prebuilt 元件參考。工具節點的參數行為與版本差異請以官方文件為準。
  • tenacity 官方文件:https://tenacity.readthedocs.io/。重試策略(stop、wait、retry 條件)的完整說明。
  • httpx 官方文件:https://www.python-httpx.org/。逾時、例外類別與 raise_for_status() 行為。
  • Tavily Search API 官方文件:查詢參數與回應格式,串接真實金鑰前請先確認當前版本的欄位定義。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...