iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Software Development

手刻 AI Agent!大一新生的 Python 實戰筆記系列 第 30 篇

Day 30 - 一個月,一個稍有成果的小作品!

  • 分享至 

  • xImage
  •  

昨天有提到,今天要來把整份程式做打包,提供給其他人使用,我本來以為打包成執行檔用下載的會比較簡單,後來發現直接在終端輸入指令安裝更方便,所以,我們今天讓程式可以透過 uv tool install 的方式下載。
在打包之前,最後再新增一個功能以及改幾個地方:


ctrl + c 捕捉退出程式

在 Day 27 時,做了當「模型回答時」按 ctrl + c 可以中斷模型的功能,
但目前如果在輸入時按下會發生什麼事呢?
沒錯,一行行的報錯,因為我們沒有做好這部分的捕捉,直譯器以為遇到了意外狀況,給出了報錯。

在使用者輸入時,加上 try ... except 來捕捉,這裡用了元組把 KeyboardInterrupt、EOFError 同時做捕捉,
前者是之前寫過的 ctrl + c;後者則是 ctrl + d 的 End-Of-File Error(檔案結尾、輸入串流結束錯誤)。

End-Of-File Error:
在命令列或終端機程式中,當程式正在等待使用者輸入(例如呼叫 Python 的 input() 或 prompt_toolkit 的 prompt()),而標準輸入串流(stdin)卻突然被關閉且沒有讀到任何資料時,Python 就會拋出這個錯誤。

sys.exit(0),0 是結束代碼,代表「正常結束」。

# src/meowgent/cli/main.py
...
if __name__ == "__main__":
	...
	try:
		while ...:
			try:
                user_input, images = get_input(rprompt=model.get_context_status_text)
            except (KeyboardInterrupt, EOFError):
                cli.console.print(cli.render_end())

                sys.exit(0)

            if not user_input ...:
	            ...

加入基礎路徑

一直以來,我匯入專案內的其他模組(檔案)的方式都是直接用例如 from cli import get_input 這樣的方式匯入,而之前都沒有出錯,是因為我在終端開啟程式的位置是在 meowgent/src/meowgent 裡頭,直譯器一上門就發現了「cli 檔案夾在那,我要進去裡面找!」。
然而打包後就不同了,執行的起點位於最外層的 meowgent,直譯器一來,找不到 cli 在哪裡,就直接報錯了,這裡有兩種改法:

  1. 把「所有」專案內的匯入改為如 from meowgent.cli import get_input 這樣的匯入方式。
  2. 在 main.py 中,把 meowgent/src/meowgent 加入「模組搜尋路徑清單」,讓直譯器知道可以在這裡面找模組。

可以看出,前者是非常麻煩的,所以我選擇後者(當然,如果一開始就用這樣的方式匯入,也不至於這麼麻煩)。

所以現在 main.py 的匯入大致長這樣:

主要看 BASE_DIR 開始的部分:

先定義出 meowgent/src/meowgent 的路徑,
從當前檔案 meowgent/src/meowgent/cli/main.py 的位置開始推,然後要先 resolve() 把前面的目錄展開,parent 是 cli 目錄,再往上一層 parent.parent 才是我們所求的 meowgent/src/meowgent。

然後,檢查它是否有在「模組搜尋路徑清單」- sys.path 串列裡,如果沒有的話加入到串列的第一個元素。
(不放在第一個其實也沒差,主要是防止有其他的套件跟我們的檔案夾同名時,其他套件因為排序在前面而被優先使用,但在本專案內沒有這種狀況)

# src/meowgent/cli/main.py
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from providers import LLMResponse

# -----

from pathlib import Path
import sys

BASE_DIR = Path(__file__).resolve().parent.parent # 取得 src/meowgent 位置

if str(BASE_DIR) not in sys.path:
    sys.path.insert(0, str(BASE_DIR))
    
# -----

from agent import Agent
from providers import OllamaProvider
from cli import get_input, get_tool_approval, CLIRenderer, start_ollama, clean_ollama, handle_cmd, select_directory
import os
from config import ConfigManager, repair
import ollama
import threading
from typing import Optional
from tool import set_subagent_callback

記住以下原則:

  1. from __future__ import annotations 的部分一定要在「第一行」。
  2. 專案內寫出的模組一定要在將 BASE_DIR 加入到 sys.path 之後才能匯入(除非你用方法一)。

外部的模組不一定要在路徑加到 sys.path 才能匯入,也可以放在前面,而 Path 跟 sys 因為要先用到,一定要先寫出來。


main 入口

先前在 main.py 時我們寫的入口是 if __name__ == "__main__":,
意思是「當呼叫者為程式本身才執行」,但打包後會有問題,因為呼叫者不是 main.py 本身了(變成了指令封裝腳本)。
把程式入口改成 def main():(宣告為程式進入點),然後下方我們也保留原先的 if __name__ == "__main__": 方式:

還要把 def on_subagent_status(...): 移進來,否則會報錯(因為執行作用域問題)。

# src/meowgent/cli/main.py
...

def main():
	def on_subagent_status(...):
		...
	
	cli = ...
	
	...

if __name__ == "__main__":
	main()

編寫專案設定檔 - pyproject.toml

找找看你專案裡面有沒有 pyproject.toml 這份檔案,如果是使用 uv 來下載套件的話,那應該會有 [project] 裡面的內容了,而我們還需要新增上下兩部分的內容:

  1. 打包([tool.hatch.build.targets.wheel])與建構([build-system]):
    • tool.hatch.build.targets.wheel:指定打包成 .whl 時的原始碼路徑採用 src layout(就是有寫 src/meowgent 的意思),明確告知 Hatchling 核心程式碼位於 src/meowgent。
    • build-system:宣告專案使用 Hatchling 作為建置後端。

    Hatchling 可以理解為是負責打包程式的工具。

  2. 專案核心資訊與相依套件 [project]:
    • 項目名稱、版本、python 版本以及各核心套件(包含要求版本)。
  3. 命令列執行入口([project.scripts]):
    • 當套件安裝後,系統會建立一個名為 meowgent 的執行檔,可透過輸入 meowgent 來啟動程式。
    • 執行 meowgent 時,會自動調用 src/meowgent/cli/main.py 裡面的 main() 函式。
[tool.hatch.build.targets.wheel]
packages = ["src/meowgent"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "meowgent"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
    "html2text>=2025.4.15",
    "httpx>=0.28.1",
    "live>=0.0.3",
    "mcp>=2.1.1",
    "ollama>=0.6.2",
    "openai>=2.46.0",
    "pathlib>=1.0.1",
    "prompt-toolkit>=3.0.52",
    "pydantic>=2.13.4",
    "python-dotenv>=1.2.1",
    "questionary>=2.1.1",
    "readability-lxml>=0.8.4.1",
    "regex>=2026.1.15",
    "rich>=15.0.0",
]

[project.scripts]
meowgent = "meowgent.cli.main:main"

發布

到這裡,接下來只要把你的專案放到 GitHub 上並「公開」,大家就可以透過 uv 來下載了!
舉例來說,我這份專案的下載方式為:

uv tool install git+https://github.com/LeeMatthewCat/meowgent.git

但要記得電腦裡面要有 ollama 軟體。

然後啟動:

meowgent

心得部分

終於啊~30 天的文章,順利被我寫完了,接下來這邊,就來說說我寫這份專案及文章的心得吧!


意義?

過程中,我無數次地懷疑過自己,到底寫這東西做什麼,我寫的,也沒有人家 codex、claude code 好用啊!

後來,我也釋懷了,在學校,我開著電腦寫程式時,總會有人問我「你寫這做什麼?」,我都會跟他說「我寫來玩玩的」。
沒錯,真的就是寫來玩的,更認真點來說是寫來「練習」的,沒錯,就像我參賽介紹中寫的「身為剛升大一的新鮮人,我想趁著暑假扎實磨練 Python 能力,因此決定挑戰從零打造屬於自己的 AI Agent!」,我的目的就只是為了練習 Python 而已,並不是要寫出什麼舉世矚目的專案出來,我沒有那麼厲害。


看似 30 天,實則 3 個月

這份專案其實我默默寫了很久了,第一個 commit 是在 7/25 就提交了,但印象中 7 月初甚至更早我就開始做了。

在 8 月底,我舅舅建議我可以把這份專案拿去參加今年的 iThome 鐵人賽(他去年也參加了,而且還拿到冠軍,超強的)。

隨後,我開始看著已一個月沒碰的 ollama_provider.py 等前期 code 寫起了文章,
寫文章的過程中,發現了好多當初沒寫好,或已經看不懂的部分,我又重新把它改正、理解,甚至重構。

而到了 9/13,也就是 Day 1 發布當天,我把「文章的雛形」已經寫到了 Day 18,
沒錯,蠻好笑的,我也不知道那時候為什麼不一次寫完,哈哈。
而那時,我提到未來想做的功能:

  1. Agent 的設定功能。
  2. 多模態處理(讀取圖片)。
  3. 上下文壓縮、記憶管理。
  4. 多 Agent 調度。
    只有記憶體管理的部分沒有實現,整體來說還算是滿意的,而除此之外,還多了設定檔、上下文用量顯示這兩個功能。

有把前面幾篇都看到最後的人,應該都有發現,我一直在說我後面幾天都是壓線上傳的,但不是在 Day 1 時就已經寫到 Day 18 去了嗎?
因為我只寫了個雛形,後面再看時發現很多地方好像不至於分這麼多天,把它合併了,甚至有一篇最後因為我後面加了新功能不需要了而被刪掉了。

還有,不知道有沒有人發現,我後期的文章是一篇比一篇還要長啊,寫得越來越詳細了。
(當然,還有因為我開學了,越來越忙了,暑假真的好開心,每天只要待在家寫程式就好)

所以,我可以如期地繳出這 30 天的內容,自己還是覺得非常慶幸的。

這裡要自首一下,後期有幾篇,其實我都是在大致寫完後就先趕緊上傳了,然後過了 12:00 再用編輯模式把修好的內容補上,非常抱歉啊。


沒有 AI 我可能要花上大半年

整份專案包括文章,我都使用到了大量的 AI 來幫忙。

但是基本上,每行程式碼都是我打的。

如果沒有它,才學會 Python 半年的我,估計是沒辦法在這麼短的期間內,完成相對完整的大專案。

真的沒有想到我寫了這麼多。
https://ithelp.ithome.com.tw/upload/images/20261012/20183608iJAX9hfeHV.png

我想新增什麼功能,就把想法告訴它;出了什麼 bug 就告訴它,叫它幫我解;就連文章寫完,我也叫它幫我檢查錯字(而我錯字真的好多,不知道有沒有沒被它抓到而被你們抓到的錯字呢?)。


是天使同樣也是惡魔

我時常在想 AI 到底會不會取代寫程式這件事,從 2017 年 Transformer 出現至今,僅僅過去了 9 年,AI 已經可以做到絕大部分的 coding,只需要人從旁給 prompt。
而今年我才大一,大學四年碩士兩年,出來還要當個兵,我真正出社會已經又是 7 年後了,很難想像之後會發展成什麼樣。


學到了?下一步呢?

這份專案加上這個文章,我很明顯感受到自己 Python 的能力提升了不少,
而我的時間規劃能力也是有待加強,剛開始很有信心地覺得自己一定來得及,寫一寫就開始在旁邊滑手機,後面發現來不及了才在那趕。

而我又看到了個有趣的專案 https://github.com/ARahim3/DigUp/tree/main ,接下來,我想模仿他來自己再寫一份專案,
預計是用 MLX 來做較貼合於 Mac 的運算,然後跟他一樣採用 Google 的 EmbeddingGemma 2 模型來做檔案轉換為空間向量的部分,
然後打算先採用已經熟悉的 CLI 介面做開發,後期再嘗試跟他一樣用 Swift 做前端。

至於會不會再把它給寫成文章,那就要再看看啦,
可以確定的是,我不會想要像這 30 天一樣每天更新,太累了而且感覺品質也不是很好。


最後,感謝大家!

非常感謝每位曾經看過我這 30 篇文章的人,我有時候都在想,我寫出來的東西我自己都不一定想看了,真的有人要看嗎?

所以真的非常謝謝看到這裡的各位!

「手刻 AI Agent!大一新生的 Python 實戰筆記」正式完結~


上一篇
Day 29 - 上下文壓縮 - 下
系列文
手刻 AI Agent!大一新生的 Python 實戰筆記 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
CY Hung
iT邦新手 5 級 ‧ 2026-10-12 23:41:12

恭喜完賽
我覺得AI真的大幅降低了跨領域學習的門檻

謝謝~
真的,善用 AI 可以學的恨快

0
mrkt
iT邦新手 4 級 ‧ 2026-10-13 09:05:48

恭喜完賽!
從讀寫檔案、執行指令到打包安裝,能看到你一步步把程式做完整。之後如果還有維護 GitHub,可以放幾個能直接跑的小範例,也在 README 分享不同模型的結果和耗時,讓想試用的人更容易上手、選模型。

謝謝支持!

我要留言

立即登入留言