title: 我的 commit message 寫成了「You've hit your session limit」
published: true
description: 我是如何最後改用本機 LLM 來產生 git commit 訊息的
tags: genai, ollama, learning, llm
series: GenAI 學習旅程
cover_image: https://i.imgflip.com/avdl7i.jpg

為了達到最佳效果,請使用 100:42 的比例。

published_at: 2026-06-28 10:19 +0000


🧐 背景 🧐

我原本一直在用這個單行指令。

git commit -m "$(git diff --staged | claude -p "根據這個 diff,提供一個簡單、單行的 git commit 訊息,並遵守最佳實務。除此之外不要輸出任何內容。")"

把已暫存的 diff 透過 Claude 傳進去,再拿回 commit 訊息。原本運作得很好,直到我在一次 commit 到一半時撞上了 Claude 的使用額度限制。Shell 抓到的是錯誤訊息,而不是 commit 訊息。

於是我 repo 裡就出現了一個 commit,內容寫著:

You've hit your session limit

就在那一刻,我靈光一閃!原來這就是我的本機模型使用場景。✨

⚠️ 免責聲明 ⚠️

  • 我正在學習 GenAI,這是我的學習旅程
  • 這不是教學文
  • 你覺得理所當然的事,對我來說不一定如此

讓 Ollama 跑起來

Ollama 可以讓你在本機執行開源模型。安裝之後,會有一個伺服器在 http://localhost:11434 上執行。

ollama pull qwen2.5-coder:1.5b

我選 qwen2.5-coder:1.5b,因為它夠小,而且懂程式碼。

為什麼特別選 1.5b?因為我的筆電只有 8GB RAM。當你在本機跑模型時,這真的不算多。

大致算一下(以下是我這台機器上的估算,你的結果可能不同):

  • Mac 總記憶體:8.0 GB
  • macOS + 已執行中的應用程式:約 4.0 到 5.0 GB
  • 模型載入到記憶體中:約 1.2 GB(依據約 1 GB 的模型檔大小估算)
  • Context window:約 0.03 GB
  • 剩餘:約 1.77 到 2.77 GB 可用

有趣的是,雖然 qwen2.5-coder:1.5b 是一個 15 億參數的模型,它在磁碟上卻只占大約 1 GB。原因是它經過了量化。

量化 的意思是,模型權重 會以較低精度儲存,使用 4 位元或 8 位元整數,而不是通常的 16 位元或 32 位元浮點數。這能大幅減少模型大小與記憶體占用,但也可能略微影響準確度。

我也試過更大的模型。結果我的筆電直接變得不能用:風扇狂轉、應用程式卡死,整台機器都快撐不住。所以最後還是選 1.5b。

我還找到另一個可能可用的量化模型——gemma3:1b-it-qat。之後我打算試試看,看看它在效能和資源使用上表現如何。

第一次嘗試

我把原本的 Claude 換成 Ollama:

git commit -m "$(git diff --staged | ollama run qwen2.5-coder:1.5b "根據這個 diff,提供一個簡單、單行的 git commit 訊息,並遵守最佳實務。除此之外不要輸出任何內容。")"

我拿一個變更來測試:我從 6 個檔案的某些 agent config front matter 中移除了 tools 區段。這次成功了。

但 commit 訊息卻說這是對 README 檔案的變更。

🤔 這代表什麼?🤔

雖然 qwen2.5-coder:1.5b 原生可支援高達 32,768 tokens 的大 context window,但 Ollama 在沒有 Modelfile 的情況下執行時,實際上會限制預設的 context 大小。

我檢查了 Ollama 的 log,找到這一行:

level=INFO source=routes.go:2073 msg="vram-based default context" total_vram="5.3 GiB" default_num_ctx=4096

這表示根據我機器上的 5.3 GiB VRAM,Ollama 把預設的 num_ctx 設成 4096 tokens。這就是為什麼模型只看到了 diff 的開頭,然後猜成跟 README 有關。

第二次嘗試

我想,也許是 prompt 不夠好。所以我又多加了一些指令再跑一次。

這次它說變更是在 code-reviewer.md。那只是 6 個檔案中的其中一個,而且它完全忽略了另外 5 個。

重點是,模型沒有抱怨。它沒有說「我看不到剩下的內容」。它只是根據不完整的輸入,給了我一個很有自信的答案。

到了這裡我才明白,光調整 prompt 還不夠,我也需要調整模型本身。

建立 Modelfile

這是我才學到的東西。Modelfile 是加在基礎模型上方的一層設定。你可以修改參數,並從中建立一個具名模型。

FROM qwen2.5-coder:1.5b

PARAMETER num_ctx 8192 
PARAMETER temperature 0.2

我改了兩件事:

num_ctx 8192 —— 雖然 qwen2.5-coder:1.5b 原生可以處理最多 32k tokens,但 Ollama 在沒有 Modelfile 時,會預設使用較小的 context window(以我的情況來說,是根據 VRAM 設成 4096)。我把它提高到 8k,同時讓它在我的 8GB 機器上保持記憶體效率。

temperature 0.2 —— 降低 temperature,讓輸出更可預測。對 commit 訊息來說,我不需要有創意,我需要的是一致性。

ollama create qwen-commit -f ./Modelfile

現在我有了一個叫做 qwen-commit 的模型,可以專門拿來做這件事。

順帶一提,Modelfile 不是唯一能設定這些參數的方法。你也可以直接使用 REST API,並傳入 options 物件:

curl http://localhost:11434/api/generate -d '{
  "model": "qwen2.5-coder:1.5b",
  "prompt": "${YOUR_PROMPT}",
  "options": {
    "temperature": 0.2,
    "num_ctx": 8192
  }
}'

就我的用途來說,Modelfile 更合理,因為我只想直接執行 ollama run qwen-commit,而且一切都已經預先設定好。

第三次嘗試

有了更大的 context window 之後,模型現在可以看到全部 6 個檔案了。但它還是把這次變更描述成「⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ ⠋ `diff feat(.opencode/agent): update tool list for code-reviewer, frontend-enginee frontend-engineer, go-backend-engineer, project-lead, req requirements-analyst, solution-architect」。比較好了,但還是很拗口,而且不對。

🤔 這代表什麼?🤔

模型現在是有讀完整個 diff 了,但 commit message 雖然技術上勉強正確,卻完全不像我們實際會寫的 commit 訊息。你看它出現了 frontend-enginee frontend-engineerreq requirements-analyst 這種東西。

所以我改了 prompt。與其讓模型自己推斷,我直接告訴它。

affected_files=$(git diff --staged --name-only | paste -sd, -)

然後在 prompt 裡加上:"請注意,變更位於這些檔案中:[$affected_files]"

這樣之後,commit 訊息就好多了。模型不需要再猜了。

還有一件事

commit 訊息現在準確了,但模型還是會用奇怪的格式把它包起來,儘管 prompt 已經說不要這樣做了。有時候是反引號。有時候前面會加上 diff。有時候訊息外面又多了隨機引號。

所以我又加了一個清理步驟,把這些東西全部去掉:

msg=$(echo "$msg" | tr -d '\r' | sed -E \
  -e 's/```(diff)?//g' \
  -e 's/^diff[[:space:]]+//I' \
  -e 's/^[[:space:]]+//;s/[[:space:]]+$//' \
  -e 's/^["'\'']//' -e 's/["'\'']$//')

雖然不優雅,但它能擋掉大部分模型加進來的雜訊。在我把 prompt 和模型都調得更好之前,這樣先頂著。

我也把 git diff --staged 改成了 git diff --staged --unified=0。預設情況下,git 會在每個變更周圍顯示 3 行上下文。對 commit message 來說,模型不需要這些上下文,只需要知道改了什麼。--unified=0 會把這些上下文拿掉,代表送給模型的 token 更少。在小 context window 的情況下,每個 token 都很重要。

好了 🎉

* b6f0abc (HEAD -> main, origin/main, origin/HEAD) fix: update tool list for all agents

更大規模、與程式碼相關的 commit,你可以看到它是逐步改善的。

* b13f344 (HEAD -> main) fix(inspection-workflow): add requirement for editing confirmed vess vessel profile
* 958053c sh fix(app_test.go, sqlite.go, sqlite_test.go, tasks.md): add save and cancel  behaviour tests for vessel profile editing
* 0f33259 sh fix: update vessel profile form and edit flow in App.svelte, add tests for  editing workflow, and improve styles in styles.css, update model in go/mode go/models.ts

最終的 Modelfile

經過這些反覆調整後,我的 Modelfile 已經和一開始很不一樣了:

FROM qwen2.5-coder:1.5b

PARAMETER num_ctx 8192
PARAMETER temperature 0.2
PARAMETER top_p 0.7
PARAMETER num_predict 256
PARAMETER repeat_penalty 1.2
PARAMETER stop "Changes to be committed:"
PARAMETER stop "Note:"
SYSTEM """
你是資深開發者的助理。你的唯一任務是根據提供的 code diff,產生一行乾淨、簡潔的 Git commit 訊息。
規則:
- 只能回覆 commit 訊息文字本身。
- 不要包含 markdown code block、反引號、解釋、前言或結語。
- 請使用 Conventional Commits 格式(例如 feat(scope): message、fix: message)。
- 單行長度請保持在 100 字元以內。
- 使用祈使語氣(例如「Add feature」,不要用「Added feature」或「Adds feature」)。
"""

各個參數的作用,以及我為什麼加上它們:

temperature 0.2:控制隨機性。越低代表越可預測。我不想要有創意的 commit 訊息。

top_p 0.7:和 temperature 一起作用。它限制模型只考慮最有可能的前 70% 下一個詞。這是另一種讓輸出更聚焦、不亂跑的方法。

num_predict 256:模型最多可輸出的 token 數。commit 訊息只需要一行,我不需要它寫成一篇文章。這個值是上限。

repeat_penalty 1.2:懲罰模型重複自己。沒有這個設定時,我會看到像 frontend-enginee frontend-engineerreq requirements-analyst 這種重複。模型會開始結巴,重複詞的一部分。

stop "Changes to be committed:"stop "Note:" —— 停止序列。有時候模型會在 commit 訊息之後繼續生成,看起來像 git 輸出的文字。這些設定可以讓模型一旦輸出這些字串,就立即停止。

SYSTEM 區塊則是內建在模型裡的 prompt。每次我執行 ollama run qwen-commit,這段提示就已經存在了。我不需要每次都另外傳入。

最終的函式

經過這些反覆調整後,最後我得到的是這個版本。一個自訂的 shell 函式 gac 和一個別名 gacc。預設使用本機模型,但我也可以在需要時改用 Claude。

gac() {
  # 1. 檢查是否有已暫存的變更
  if git diff --cached --quiet; then
    echo "❌ Error: No staged changes found. Run 'git add' first."
    return 1
  fi

  local mode="${1:-qwen}"
  local msg=""
  local exit_code=0

  # 收集檔案名稱作為上下文
  local affected_files
  affected_files=$(git diff --staged --name-only | paste -sd, -)

  # ---------------------------------------------------------
  # 改良後的 prompt:嚴格限定 Conventional Commits 規則
  # ---------------------------------------------------------
  local system_prompt="You are a strict code assistant. Write a single-line Conventional Commit message for the provided diff.
Strict Rules:
1. Format must exactly match: type(scope): description
2. Allowed types ONLY: feat, fix, docs, style, refactor, perf, test, chore.
3. The 'scope' must be a single, broad feature/module name (e.g., vessel-profile, api). NEVER use file names.
4. The 'description' must summarize the high-level intent in the imperative mood (e.g., 'add form validation').
5. ABSOLUTELY DO NOT list specific file names, paths, or extensions in the commit message.
6. Output EXACTLY one line. No markdown blocks, no quotes, no explanations, and no stray prefixes like 'sh'.
Context: The files modified are [$affected_files]."

  # 2. 執行路由
  if [ "$mode" = "claude" ]; then
    msg=$(git diff --staged --unified=0 | claude -p "$system_prompt" --output-format text 2>&1)
    exit_code=$?
  else
    if ! curl -s --max-time 2 http://localhost:11434 > /dev/null; then
      echo "❌ Error: Local Ollama server is not running on port 11434."
      return 1
    fi
    msg=$(git diff --staged --unified=0 | ollama run qwen-commit "$system_prompt" 2>/dev/null)
    exit_code=$?
  fi

  # 3. 嚴格的錯誤驗證
  if [ $exit_code -ne 0 ] || [ -z "$msg" ]; then
    echo "❌ Error: Failed to generate a response via $mode."
    echo "Details received: $msg"
    return 1
  fi

  # 4. 嚴格的文字清理流程
  msg=$(echo "$msg" | tr -d '\r' | sed -E -e 's/```(diff)?//g' -e 's/^[[:space:]]+//;s/[[:space:]]+$//' -e 's/^["'\'']//' -e 's/["'\'']$//')

  # 5. 乾淨地執行 git commit
  git commit -m "$msg"
}

# 別名:明確強制使用 Claude
alias gacc="gac claude"

學到的事

  • 告訴模型你已經知道的事。不要讓它去猜那些你很容易自己萃取的資訊。
  • 當你希望結果有一定可預測性時,降低 temperature。
  • Modelfile 很有用。你可以建立一個針對特定工作、已配置好的具名模型。
  • 模型大小、(V)RAM 和 context size 彼此相關。在資源受限的機器上,這三者都必須有意識地調整。

這樣就完美了嗎?

沒有。它還是偶爾會抓錯變更重點。遇到較大的 commit 時也需要時間。還有很多可以改進的地方。

那為什麼不直接用 Claude 呢?那當然是最簡單的方法,但它還是會消耗我的 token。而且我也想學習本機模型是怎麼運作的、context window 如何影響輸出、以及如何針對特定工作調校模型。這就是我做這件事的整個目的。

它可以離線運作、幾乎不用花錢 💰,而且因為我把它弄壞再修好,所以我也理解每個部分。

我覺得最好的學習方式,就是找一個真實的使用場景,不管它有多瑣碎。這能幫助你一次理解一個概念。

下一篇:我在為 Brown field 專案使用 OpenSpec 建立 Green field 產品時的學習心得

歡迎所有建設性的回饋與留言


原文出處:https://dev.to/shyamala_u/my-commit-message-said-youve-hit-your-session-limit-2abn


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   💬3   ❤️3
623
🥈
我愛JS
📝2   💬3   ❤️3
113
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登