CI/CD中的AI如何用Agent自动生成Release Note
龙虾不加班
发 Release 最烦的不是写代码,是写 Release Note。
你在 GitHub 上看到一个 Release,写了 “Bug fixes and performance improvements”——作者的心态跟我一样:代码写完了,真不想再花 20 分钟组织语言总结改了什么。
但这个活 AI 特别擅长——读 Git 提交记录,分类整理,生成人类可读的 Release Note。
这正是我开源的 daily-report-agent 做的事。给它换个 Prompt,它就能从 Git 历史生成 Release Note 而不是日报。
这篇带你把这个能力嵌入 GitHub Actions,每次发 Release 自动生成变更说明。
整体流程
Git Push (触发 workflow)
→ GitHub Actions 拉取代码
→ 用 daily-report-agent 分析 git log
→ Agent 调用 LLM 分类 & 组织语言
→ 生成 Release Note Markdown
→ 自动发布到 GitHub Releases第一步:把生成逻辑抽成一个独立命令
在 cmd/agentd/main.go 里除了 API 服务,再加一个 release-note 子命令:
// cmd/agentd/release.go
package main
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"time"
"agent-project/internal/config"
"agent-project/internal/llm"
)
// GenerateReleaseNote 从 Git 历史生成 Release Note
func GenerateReleaseNote(cfg *config.Config, fromTag, toTag string) (string, error) {
// 1. 获取提交记录
commits, err := getGitCommits(fromTag, toTag)
if err != nil {
return "", fmt.Errorf("获取提交记录失败: %w", err)
}
if len(commits) == 0 {
return "无新提交", nil
}
// 2. 构造 Prompt
prompt := buildReleaseNotePrompt(commits, fromTag, toTag)
// 3. 调用 LLM
client := llm.NewClient(cfg.LLM)
resp, err := client.Chat(context.Background(), []llm.Message{
{
Role: "system",
Content: releaseNoteSystemPrompt,
},
{
Role: "user",
Content: prompt,
},
})
if err != nil {
return "", fmt.Errorf("LLM 调用失败: %w", err)
}
return resp.Text, nil
}
// getGitCommits 获取两个 tag 之间的提交记录
func getGitCommits(fromTag, toTag string) ([]Commit, error) {
rangeStr := "HEAD"
if fromTag != "" && toTag != "" {
rangeStr = fmt.Sprintf("%s..%s", fromTag, toTag)
} else if fromTag != "" {
rangeStr = fmt.Sprintf("%s..HEAD", fromTag)
}
cmd := exec.Command("git", "log", rangeStr,
"--pretty=format:%H|||%an|||%ad|||%s|||%b",
"--date=short",
)
output, err := cmd.Output()
if err != nil {
return nil, err
}
var commits []Commit
for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
if line == "" {
continue
}
parts := strings.SplitN(line, "|||", 5)
if len(parts) < 4 {
continue
}
body := ""
if len(parts) == 5 {
body = parts[4]
}
commits = append(commits, Commit{
Hash: parts[0][:7],
Author: parts[1],
Date: parts[2],
Subject: parts[3],
Body: body,
})
}
return commits, nil
}
type Commit struct {
Hash string
Author string
Date string
Subject string
Body string
}
// buildReleaseNotePrompt 构造分类 Prompt
func buildReleaseNotePrompt(commits []Commit, fromTag, toTag string) string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("从 %s 到 %s 的变更:\n\n", fromTag, toTag))
for _, c := range commits {
sb.WriteString(fmt.Sprintf("- [%s] %s (%s, %s)\n",
c.Hash, c.Subject, c.Author, c.Date))
if c.Body != "" {
// 只取 body 的第一段
bodyLines := strings.Split(c.Body, "\n\n")
if len(bodyLines) > 0 {
sb.WriteString(fmt.Sprintf(" 详情: %s\n", bodyLines[0]))
}
}
}
sb.WriteString(fmt.Sprintf("\n共 %d 个提交。请生成 Release Note。", len(commits)))
return sb.String()
}
const releaseNoteSystemPrompt = `你是一个开源项目的 Release Manager。
根据 Git 提交记录生成 Release Note。
分类规则:
- 🚀 新功能:feat 开头的提交
- 🐛 Bug 修复:fix 开头的提交
- 🔧 改进:refactor/perf/style 开头的提交
- 📝 文档:docs 开头的提交
- ⚠️ 破坏性变更:有 BREAKING CHANGE 标记或明显不向后兼容的提交
如果不能归类的,放在 "其他" 里。
格式要求:
- 使用 Markdown
- 每个条目一行,说明变更内容,不写过程
- 避免 "该同志"、"表现优异" 等套话
- 语言:中文
输出格式:
## 🚀 新功能
- xxx
## 🐛 Bug 修复
- xxx
## 🔧 改进
- xxx
## 📝 文档
- xxx
如有破坏性变更,在开头加:
> ⚠️ 本版本包含破坏性变更,请谨慎升级。`第二步:写 GitHub Actions Workflow
# .github/workflows/release-note.yml
name: Generate Release Note
on:
push:
tags:
- 'v*' # 推送 v 开头 tag 时触发
workflow_dispatch: # 也支持手动触发
inputs:
from_tag:
description: '从哪个 tag 开始'
required: false
to_tag:
description: '到哪个 tag'
required: false
jobs:
generate-release-note:
runs-on: ubuntu-latest
permissions:
contents: write # 需要写权限来创建 Release
steps:
- name: 拉取代码
uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整 git 历史
- name: 安装 Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: 编译 agent
run: go build -o agentd ./cmd/agentd
- name: 确定 tag 范围
id: tags
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "FROM=${{ inputs.from_tag }}" >> $GITHUB_OUTPUT
echo "TO=${{ inputs.to_tag }}" >> $GITHUB_OUTPUT
else
# 自动获取:上一次 tag → 当前 tag
CURRENT_TAG="${{ github.ref_name }}"
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
echo "FROM=${PREV_TAG}" >> $GITHUB_OUTPUT
echo "TO=${CURRENT_TAG}" >> $GITHUB_OUTPUT
fi
- name: 生成 Release Note
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_MODEL: deepseek-v4-flash
run: |
./agentd release-note \
--from "${{ steps.tags.outputs.FROM }}" \
--to "${{ steps.tags.outputs.TO }}" \
> release-note.md
- name: 展示生成的 Release Note
run: cat release-note.md
- name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
body_path: release-note.md
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}真实效果
以 daily-report-agent v0.2.0 为例,手动写 Release Note 大约花了 15 分钟。用 Agent 自动生成后:
Agent 生成的输出:
## 🚀 新功能 - 支持多仓库批量分析,自动汇总日报 (#15) - 新增企业微信通知渠道,日报直接推送到群 (#18) ## 🐛 Bug 修复 - 修复 Git 提交信息中含特殊字符时解析崩溃的问题 (#12) - 修复时区问题导致日报日期差一天 (#14) ## 🔧 改进 - LLM 调用增加指数退避重试,API 超时自动恢复 (#16) - 日志改用 slog 结构化输出,方便接入日志平台 (#17) ## 📝 文档 - README 新增 Docker Compose 部署说明 - 补充 API 文档和调用示例
我审阅了一下,改了两处措辞,就发布了。全程 3 分钟。
Agent 不会漏提交——Git log 是完整的。它也不会把 feat 写成 fix。比人更快,比人更准确。
进阶:多仓库 Release Note 汇总
如果你有一个微服务项目,一次发版涉及 5 个仓库。Agent 可以逐个仓库分析,再汇总:
func GenerateMultiRepoReleaseNote(repos []string) (string, error) {
var allNotes []string
for _, repo := range repos {
note, err := GenerateReleaseNoteForRepo(repo)
if err != nil {
log.Printf("[Warn] 仓库 %s 生成失败: %v", repo, err)
continue
}
allNotes = append(allNotes, fmt.Sprintf("### %s\n\n%s", repo, note))
}
// 调一次 LLM 做最终汇总
return summarizeReleaseNotes(allNotes)
}成本
一次 Release Note 生成:
| 步骤 | Token 消耗 | 费用 |
|---|---|---|
| Git Log 提取 | 0(本地执行) | ¥0 |
| LLM 分类整理 | ~3000 token | ¥0.009 |
| GitHub API 发布 | 0 | ¥0 |
| 合计 | ~3000 token | ¥0.009 |
不到 1 分钱。省了 15 分钟。
用 GitHub Actions 免费额度(每月 2000 分钟),完全在免费额度内。
不到1分钱省了15分钟。下一篇:Agent 安全审查清单——API Key 管理、用户数据隔离。10个检查项,每个配修复方案。
到此这篇关于CI/CD 中的 AI:用 Agent 自动生成 Release Note的文章就介绍到这了,更多相关CI/CD Agent 自动生成 Release Note内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!
