写 commit message 是我最讨厌的开发环节之一。改了二十个文件,最后憋出一句 “fix bugs”。最近用 OpenClaw 搭了个小工具,能根据 git diff 自动生成规范的 commit message 和 PR 描述,记录一下过程。

一、目标和配置

做一个 CLI 工具:git-ai commit 读取暂存区 diff 生成 Conventional Commits 格式的 message,git-ai pr 读取分支变更生成 PR 描述。

OpenClaw 的自定义 Provider 配置(~/.openclaw/openclaw.json):

{
  "models": {
    "providers": {
      "tikhub": {
        "baseUrl": "https://ai.tikhub.io/v1",
        "api": "openai-completions",
        "apiKey": "你的Key",
        "models": [
          { "id": "gpt-4o", "name": "GPT-4o", "contextWindow": 128000, "maxTokens": 16384 },
          { "id": "gpt-4.1-nano", "name": "GPT-4.1 Nano", "contextWindow": 1047576, "maxTokens": 32768 },
          { "id": "gemini-2.5-flash", "name": "Gemini 2.5 Flash", "contextWindow": 1048576, "maxTokens": 8192 }
        ]
      }
    },
    "defaults": { "model": { "primary": "tikhub/gpt-4o" } }
  }
}

二、核心代码

在 OpenClaw 里描述需求后,两分钟生成了项目骨架,以下是微调后的核心文件:

git_ai/llm.py

import os
import requests

def call_llm(prompt: str, system_prompt: str = "", model: str = "gpt-4o") -> dict:
    base_url = os.getenv("AI_BASE_URL", "https://ai.tikhub.io/v1")
    api_key = os.getenv("AI_API_KEY")
    if not api_key:
        raise ValueError("请设置环境变量 AI_API_KEY")

    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": prompt})

    resp = requests.post(
        f"{base_url}/chat/completions",
        headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"},
        json={"model": model, "messages": messages, "temperature": 0.3, "max_tokens": 2048},
        timeout=60
    )
    if resp.status_code != 200:
        raise RuntimeError(f"API 错误: {resp.status_code} - {resp.text}")

    data = resp.json()
    usage = data.get("usage", {})
    return {
        "content": data["choices"][0]["message"]["content"],
        "input_tokens": usage.get("prompt_tokens", 0),
        "output_tokens": usage.get("completion_tokens", 0),
    }

git_ai/commands.py

import click, subprocess
from .llm import call_llm

def run_git(args):
    r = subprocess.run(["git"] + args, capture_output=True, text=True)
    return r.stdout.strip()

COMMIT_PROMPT = """你是 Git commit message 生成助手。根据 diff 生成 Conventional Commits 格式:
- 第一行:<type>(<scope>): <description>(不超过 72 字符)
- 空行后写详细说明(改动较大时)
- type: feat/fix/refactor/docs/style/test/chore/perf
- 用中文。只输出 message。"""

PR_PROMPT = """根据 diff 和 commit log 生成 PR 描述,格式:
## 改动概述 / ## 主要变更 / ## 测试建议。用中文。"""

@click.group()
def cli():
    """AI Git 助手"""
    pass

@cli.command()
@click.option("--model", default="gpt-4o")
@click.option("--auto", is_flag=True, help="跳过确认直接提交")
def commit(model, auto):
    """根据 diff 生成 commit message"""
    diff = run_git(["diff", "--cached"])
    if not diff:
        click.echo("暂存区没有变更,请先 git add"); return

    if len(diff) > 15000:
        diff = diff[:15000] + "\n... (truncated)"

    result = call_llm(diff, system_prompt=COMMIT_PROMPT, model=model)
    click.echo(f"--- 生成的 commit message ---\n{result['content']}")
    click.echo(f"--- tokens: {result['input_tokens']}+{result['output_tokens']} ---\n")

    if auto or click.confirm("使用这个 message 提交?"):
        subprocess.run(["git", "commit", "-m", result["content"]])

@cli.command()
@click.option("--base", default="main")
@click.option("--model", default="gpt-4o")
def pr(base, model):
    """生成 PR 描述"""
    diff = run_git(["diff", f"{base}...HEAD"])
    log = run_git(["log", f"{base}..HEAD", "--oneline"])
    if not diff:
        click.echo("没有变更"); return

    prompt = f"Commit 记录:\n{log}\n\n代码变更:\n{diff[:20000]}"
    result = call_llm(prompt, system_prompt=PR_PROMPT, model=model)
    click.echo(result["content"])
    click.echo(f"\n--- tokens: {result['input_tokens']}+{result['output_tokens']} ---")

三、效果

export AI_BASE_URL="https://ai.tikhub.io/v1" && export AI_API_KEY="你的Key"
pip install -e . && git add . && git-ai commit
--- 生成的 commit message ---
feat(git-ai): 添加 commit message 自动生成功能

- 新增 LLM 调用层,支持 OpenAI 兼容 API
- 实现 git diff 读取与 CLI 命令
- 支持 --model 参数切换模型
--- tokens: 1842+68 ---

使用这个 message 提交? [y/N]: y

比自己写好多了。git-ai pr 也类似,自动对比分支差异输出格式化描述,直接贴 GitHub。

四、用了一周发现:烧钱

工具好用,但太费 token 了。每次 commit 要把整个 diff 丢给模型,中等改动 2000–5000 tokens,大重构到几万。一天十几次 commit,月账单预估 $40–60——光这一个小工具。

因为之前已经把 OpenClaw 配到了 ai.tikhub.io 上,本身就比官方 API 便宜一些。但我意识到折扣不是重点,模型降级才是——生成 commit message 这种模式固定的任务,根本不需要最贵的模型。

于是让 OpenClaw 帮我加了自动选模型的逻辑:

def auto_select_model(diff_length: int) -> str:
    if diff_length < 3000:
        return "gpt-4.1-nano"       # 小改动,最便宜
    elif diff_length < 10000:
        return "gemini-2.5-flash"   # 中等改动
    else:
        return "gpt-4o"             # 大重构才上 4o

实测效果:

diff 大小 模型 质量 单次费用
500 字符 gpt-4.1-nano 够用 ~$0.000003
3000 字符 gemini-2.5-flash 很好 ~$0.000035
15000 字符 gpt-4o 优秀 ~$0.000800

月均开销从 $40–60 降到 $5–8。关键不是折扣本身,而是统一接口让模型分层变得很简单

搭建不难,有意思的是日常使用中冒出来的成本问题。核心经验:用统一 API 入口降低切换成本,按任务复杂度自动选模型。这个思路不限于 Git 工具,任何高频调 LLM 的场景都适用。


参考资源:

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐