开发者因 API session 限制问题,改用本地 LLM 模型稳定生成 git commit messages,展示离线工具链的实用价值。
我曾经在用这个一行代码:
git commit -m "$(git diff --staged | claude -p "Provide a simple, one-line git commit message based on this diff following best practices. Output absolutely nothing else.")"
把 staged 的 diff 通过管道传给 Claude,获得一条 commit 消息。这个方法运行得不错,直到我在 commit 过程中用尽了 Claude 的使用限额。Shell 捕获的不是 commit 消息,而是一个错误。
所以我的代码库里有一条 commit 说的是:
You've hit your session limit
那一刻我突然意识到了!✨我找到了本地模型的用例。✨
我正在学习 GenAI,这是我的学习之旅
这不是一篇教程
对你来说显而易见的东西,对我来说可能不那么显而易见
Ollama 让你能在本地运行开源模型。安装后,你会有一个服务器运行在 http://localhost:11434。
ollama pull qwen2.5-coder:1.5b
我选择 qwen2.5-coder:1.5b 是因为它很小,而且有代码感知能力。
为什么特别是 1.5b?我的笔记本有 8GB 内存。在本地运行一个模型的时候,这不是很大的空间。
以下是粗略的内存计算(这些是基于我的机器的估计,你的可能不同):
总 Mac 内存:8.0 GB
macOS + 已运行的应用程序:约 4.0 到 5.0 GB
模型加载到内存:约 1.2 GB(基于约 1 GB 的模型文件大小)
上下文窗口:约 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。我计划有时候测试一下,看看它在性能和资源使用方面表现如何。
我在一行代码中用 Ollama 替换了 Claude:
git commit -m "$(git diff --staged | ollama run qwen2.5-coder:1.5b "Provide a simple, one-line git commit message based on this diff following best practices. Output absolutely nothing else.")"
我针对一个我从 6 个文件的 agent 配置前言中删除了 tools 部分的改动运行了它。这有效了。
Commit 消息说这是一个 README 文件的改动。
🤔 这是什么意思?🤔
尽管 qwen2.5-coder:1.5b 的原生上下文窗口为 32,768 个 token,但 Ollama 实际上在运行时没有 Modelfile 的情况下限制了默认上下文大小。
我检查了 Ollama 的日志,找到了这一行:
level=INFO source=routes.go:2073 msg="vram-based default context" total_vram="5.3 GiB" default_num_ctx=4096
它显示根据我的机器 VRAM 为 5.3 GiB,Ollama 设置了默认的 num_ctx 为 4096 个 token。这就是为什么模型只看到了 diff 的开头,并猜测了 README 文件。
我想也许我需要一个更好的 prompt。所以我再次运行它,添加了更多指令。
这一次它说改动在 code-reviewer.md。那是 6 个文件中的一个,它完全忽略了其他 5 个。
重要的是,模型没有抱怨。它没有说"我读不了其余的"。它只是基于部分输入给了我一个自信的答案。
在这一点上,我明白仅仅调整 prompt 是不够的,我需要调整模型。
这是我刚刚学到的东西。Modelfile 是一个配置层,位于基础模型之上。你可以改变参数并从中创建一个名称模型。
FROM qwen2.5-coder:1.5b
PARAMETER num_ctx 8192
PARAMETER temperature 0.2
我改变了两件事:
num_ctx 8192 —— 虽然 qwen2.5-coder:1.5b 本地可以处理多达 32k 个 token,但 Ollama 在没有 Modelfile 的情况下运行时默认使用较小的上下文窗口(在我的情况下,基于 VRAM 为 4096)。我把它提高到 8k,并在我的 8GB 机器上保持内存效率。
temperature 0.2 —— 较低的温度以获得更可预测的输出。对于 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,让所有东西都预先配置好。
使用更大的上下文窗口,模型现在可以看到全部 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 消息在技术上是正确的,但不像我们会写的 commit 消息。看看它如何有 frontend-enginee frontend-engineer 或 req requirements-analyst
所以我改变了 prompt。与其让模型自己搞清楚,我直接告诉它。
bash
affected_files=$(git diff --staged --name-only | paste -sd, -)
然后添加到 prompt 中:"Note that the changes are located in these files: [$affected_files]"
此后 commit 消息好多了。模型不再需要猜测了。
Commit 消息现在是准确的,但模型一直把它们包裹在奇怪的格式中,尽管 prompt 说不要。有时是反引号。有时是"diff"前缀。有时是消息周围的随机引号。
所以我添加了一个清理步骤来去除所有这些:
bash
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 消息,模型不需要这个周围上下文。它只需要知道什么改了。--unified=0 去除了所有这些,这意味着发送给模型的 token 更少。在小的上下文窗口上,每个 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 看起来与我开始时相当不同:
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 """
You are an expert developer's assistant. Your sole task is to generate a clean, concise one-line Git commit message based on the provided code diff.
Rules:
- Respond ONLY with the commit message text.
- Do NOT include markdown code blocks, backticks, explanations, intro text, or outro text.
- Use the Conventional Commits format (e.g., feat(scope): message, fix: message).
- Keep the one line under 100 characters.
- Use the imperative mood ("Add feature", not "Added feature" or "Adds feature").
"""
每个参数的作用以及我添加它的原因:
temperature 0.2:控制随机性。越低意味着越可预测。我不想要创意的 commit 消息。
top_p 0.7:与温度配合使用。它限制模型只考虑最有可能的下一个单词的前 70%。另一种保持输出专注的方式,不会偏离。
num_predict 256:模型可以输出的最大 token 数。Commit 消息是一行。我不需要模型写论文。这限制了它。
repeat_penalty 1.2:惩罚模型重复自己。没有这个,我会得到诸如 frontend-enginee frontend-engineer 或 req requirements-analyst 之类的东西。模型会口吃并重复单词的部分。
stop "Changes to be committed:" 和 stop "Note:" —— 停止序列。有时模型会在 commit 消息之后继续,并开始生成看起来像 git 输出的文本。这些告诉模型如果它开始输出这些字符串,立即停止。
SYSTEM 块是嵌入到模型中的 prompt。每次我运行 ollama run qwen-commit,这个 prompt 都已经在那里了。我不必每次都传递它。
经过所有迭代后,这是我最后得到的。一个自定义的 shell 函数 gac 和一个别名 gacc。它默认使用本地模型,但我也可以在需要时使用 Claude。
gac() {
# 1. Check for staged changes
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
# Gather file names for context
local affected_files
affected_files=$(git diff --staged --name-only | paste -sd, -)
# ---------------------------------------------------------
# IMPROVED PROMPT: Strict rules for 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. Execution Routing
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. Robust Error Validation
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. Strict Text Cleaning Pipeline
msg=$(echo "$msg" | tr -d '\r' | sed -E -e 's/```(diff)?//g' -e 's/^[[:space:]]+//;s/[[:space:]]+$//' -e 's/^["'\'']//' -e 's/["'\'']$//')
# 5. Run git commit cleanly
git commit -m "$msg"
}
# Alias to explicitly force Claude
alias gacc="gac claude"
告诉模型你已经知道的东西。不要让它猜测你可以轻松提取的东西。
对于需要一定确定性的任务,使用低温度。
Modelfile 很有用。你可以创建一个为特定工作配置的命名模型。
模型大小、(V)RAM 和上下文大小都是相关的。在受限的机器上,你必须有意识地处理所有三个。
不。它有时仍然会错过改动的要点。在较大的 commit 上需要花时间。还有改进的空间。
为什么不直接使用 Claude?那是最简单的事情,但它仍然消耗我的 token。我想学习本地模型是如何工作的。上下文窗口如何影响输出。如何为特定工作调整模型。这对我来说就是关键所在。
它可以离线工作,不花钱 💰,而且我理解每一块,因为我破坏了它然后修复了它。
我发现最好的学习方式是找到一个真实的用例,无论多么琐碎。它帮助你一次理解一个概念。
接下来:我在使用 OpenSpec(为 Brown field 项目设计的)构建 Green field 产品时学到的东西
我欢迎所有建设性的反馈和评论
要采取进一步的行动,你可以考虑屏蔽这个人和/或举报虐待