CI/CD中的AI如何用Agent自动生成Release Note

Written by

in

文章目录
  • 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 的变更:nn”, 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, “nn”) if len(bodyLines) > 0 { sb.WriteString(fmt.Sprintf(” 详情: %sn”, 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/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。比人更快,比人更准确。
  • 如果你有一个微服务项目,一次发版涉及 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(“### %snn%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内容请搜索风君子博客以前的文章或继续浏览下面的相关文章,希望大家以后多多支持风君子博客!
  • 目录
    • 整体流程
    • 第一步:把生成逻辑抽成一个独立命令
    • 第二步:写 GitHub Actions Workflow
    • 真实效果
    • 进阶:多仓库 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 的变更:nn", 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, "nn")
                if len(bodyLines) > 0 {
                    sb.WriteString(fmt.Sprintf("  详情: %sn", 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/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。比人更快,比人更准确。

    如果你有一个微服务项目,一次发版涉及 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("### %snn%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内容请搜索风君子博客以前的文章或继续浏览下面的相关文章,希望大家以后多多支持风君子博客!

    站内搜索