# AI 驱动的 OKR 自动生成与同步 ## Context DevPerf 已具备完整的 Git 同步管线(Gitea → gitCommits 表)和 OKR CRUD 系统。 当前 OKR 需人工创建,缺少与 Git 提交的联动。 **目标**:每次 Git 同步后,AI 分析新增 commit messages,按仓库维度: - 新增 KR — commit 提到的功能点尚无对应 KR - 更新进度 — commit 涉及已有 KR 的功能 - 标记完成 — commit 明确表示功能已完成 **分析粒度**:按仓库(每个仓库的新增 commits 做一次 AI 分析,prompt 中包含提交人信息) **OKR 归属**:commit 的提交人 = KR/Objective 的负责人(ownerId),通过 gitCommits.userId 关联 **增量机制**:每条 commit 只分析一次,分析后打标记,后续同步不再重复处理 --- ## 第一阶段:Schema 变更 **文件**: `backend/src/db/schema.ts` ### 1a. 新增 `ai_analyzed_commits` 表(增量标记,防止重复分析) ``` aiAnalyzedCommits: id varchar(50) PK commitSha varchar(200) NOT NULL UNIQUE -- 对应 gitCommits.sha batchId varchar(50) NOT NULL -- 一次 AI 调用的批次 ID createdAt datetime NOT NULL ``` **核心作用**:每次同步后,只提取 `gitCommits` 中尚未出现在此表的 commit 送给 AI。分析完成后立即写入标记,确保该 commit 不会被重复处理。 ### 1b. objectives 表加字段 - `source` varchar(50) default `'manual'` — 值: `'manual'` | `'ai_generated'` ### 1c. keyResults 表加字段 - `source` varchar(50) default `'manual'` — 值: `'manual'` | `'ai_generated'` - `sourceKey` varchar(300) nullable — AI 分配的功能标识符(如 `"user-login"`),用于语义去重 ### 1d. keyResults.status 扩展 现有 `'active' | 'paused' | 'cancelled'`,新增 `'completed'`。 字段类型是 varchar,无需改 DDL,只需让 `recalcObjectiveProgress` 正确处理 completed。 ### 1e. syncLogs.source 枚举扩展 将 `mysqlEnum('source', ['plane', 'gitea'])` 改为 `['plane', 'gitea', 'ai_okr']`。 --- ## 第二阶段:AI 配置与 LLM 客户端(豆包 Doubao) ### 2a. 环境变量 (`backend/src/config.ts`) 新增: ```typescript AI_ENABLED: z.coerce.boolean().default(false), AI_API_KEY: z.string().default(''), AI_MODEL: z.string().default('doubao-seed-2-0-pro-260215'), AI_BASE_URL: z.string().default('https://ark.cn-beijing.volces.com/api/v3'), ``` **.env 示例**: ```env AI_ENABLED=true AI_API_KEY=846b6981-9954-4c58-bb39-63079393bdb8 AI_MODEL=doubao-seed-2-0-pro-260215 AI_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 ``` ### 2b. 新建 `backend/src/services/llm-client.ts` 使用豆包(火山引擎 Ark)API,原生 fetch 实现,零额外依赖。 **豆包 API 调用格式**: ```typescript // POST https://ark.cn-beijing.volces.com/api/v3/chat/completions // Header: Authorization: Bearer {AI_API_KEY} // Header: Content-Type: application/json export async function callLLM(systemPrompt: string, userPrompt: string): Promise { const url = `${config.AI_BASE_URL}/chat/completions`; const body = { model: config.AI_MODEL, // "doubao-seed-2-0-pro-260215" messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userPrompt } ], temperature: 0.3, // 低温度保证输出稳定 response_format: { type: "json_object" } // 强制 JSON 输出 }; const response = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${config.AI_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), signal: AbortSignal.timeout(60000), // 60s 超时 }); if (!response.ok) { throw new Error(`Doubao API error: ${response.status} ${await response.text()}`); } const data = await response.json(); return data.choices[0].message.content; } ``` **要点**: - 豆包 Ark API 兼容 OpenAI `/chat/completions` 格式 - 使用 `response_format: { type: "json_object" }` 强制返回 JSON - 超时 60s,失败重试 1 次(间隔 3s) - `temperature: 0.3` 保证分析结果稳定一致 --- ## 第三阶段:核心服务 — `backend/src/services/okr-ai-sync.ts` ### 整体流程 ``` analyzeCommitsForOKR() │ ├─ 1. gatherUnanalyzedCommits() │ SELECT gc.* FROM git_commits gc │ LEFT JOIN ai_analyzed_commits aac ON gc.sha = aac.commit_sha │ WHERE aac.id IS NULL │ → 按 repoName 分组(每个仓库一批) │ ├─ 2. 遍历每个仓库的未分析 commits: │ a. 通过 projectRepos 找到该仓库关联的 projectId │ b. 获取该项目当前所有 Objectives + KRs(含 sourceKey) │ c. 构建 AI prompt(该仓库的新 commits,含每条 commit 的提交人信息 + 项目现有 OKR) │ d. 调用豆包 AI → 解析 JSON 响应 │ e. 执行 AI 返回的 actions: │ - create_objective → 创建时 ownerId = commit 提交人的 userId(AI 返回 authorUserId) │ - create_kr → 先查 sourceKey 防重 → 不存在才创建,归属到对应提交人 │ - update_progress → 更新 currentValue + 写 krLog │ - complete_kr → status='completed' + 写 krLog │ f. 重算 Objective progress │ g. 标记这批 commits 为已分析(写入 aiAnalyzedCommits) │ └─ 3. 写入 syncLogs (source='ai_okr') ``` ### 增量更新机制(核心) ``` 第一次同步: gitCommits: [A, B, C, D, E] aiAnalyzedCommits: [] → 未分析: [A, B, C, D, E] → 全部送 AI → 分析后标记 [A, B, C, D, E] 第二次同步(新增了 F, G): gitCommits: [A, B, C, D, E, F, G] aiAnalyzedCommits: [A, B, C, D, E] → 未分析: [F, G] → 只送 F, G 给 AI → 分析后标记 [F, G] 第三次同步(无新增): → 未分析: [] → 跳过,不调用 AI ``` ### AI Prompt 设计 **System Prompt**: ``` 你是一个开发团队的 OKR 管理助手。你的任务是分析 git commit 记录,管理项目的 OKR(目标与关键成果)。 你需要分析提交记录,输出 JSON 格式的操作指令: 判断逻辑: - commit 包含 "完成"、"done"、"finished"、"实现完毕" 等完成语义 → 对应 KR 标记完成 - commit 是 feat/feature 类型但未完成 → 更新对应 KR 进度(根据描述估算百分比) - commit 涉及的功能在现有 KR 中不存在 → 创建新 KR(需同时提供时间节点) - fix/refactor/chore/docs 类 commit → 只在涉及明确功能时才操作 - 不要为同一个功能创建重复的 KR,已有相同 sourceKey 的直接更新 新建 Objective 规则: - 如果提交涉及的功能没有归属到任何已有 Objective → 可以创建新 Objective - Objective 的 startDate 和 endDate 由你根据提交内容和功能复杂度判断 - period 格式为 "YYYY-Qn"(如 "2026-Q2"),根据 endDate 推算 新建 KR 规则: - targetValue = 100, unit = "%" - 根据 commit 语义估算 currentValue(初步实现=30, 基本完成=70, 完成=100) - sourceKey 用小写英文短横线格式(如 "user-login", "payment-module") - title 用中文描述 - startDate 和 endDate 由你根据功能复杂度和提交时间判断 ``` **User Prompt 模板**: ``` 仓库:{{repoName}} 所属项目:{{projectName}} 当前日期:{{today}} 该项目已有的 OKR: {{#each existingObjectives}} Objective: id={{id}}, title="{{title}}", period={{period}}, 时间={{startDate}}~{{endDate}}, 进度={{progress}}% Key Results: {{#each keyResults}} - id: {{id}}, title: "{{title}}", sourceKey: "{{sourceKey}}", status: {{status}}, 进度: {{currentValue}}/{{targetValue}}, 时间: {{startDate}}~{{endDate}} {{/each}} {{/each}} {{如果没有已有 OKR: "该项目暂无 OKR 记录。"}} 该仓库的开发人员(commit 提交人 → 系统用户映射): {{#each authors}} - userId: "{{userId}}", 姓名: {{displayName}} {{/each}} 该仓库新增的提交记录(按时间排序,均为增量,之前的已处理过): {{#each commits}} - [{{committedAt}}] 提交人: {{authorName}}(userId={{userId}}) {{sha前7位}}: {{message}} (+{{additions}}/-{{deletions}}) {{/each}} 请分析以上提交,返回 JSON。注意:每个 action 都必须带 ownerId 字段,值为提交人的 userId,表示该 OKR 归谁负责。 { "actions": [ { "type": "create_objective", "ownerId": "提交人的userId", "title": "目标标题", "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD", "reasoning": "为什么要创建这个目标", "keyResults": [ { "title": "...", "sourceKey": "...", "currentValue": number, "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD" } ] }, { "type": "create_kr", "ownerId": "提交人的userId", "objectiveId": "已有目标的id", "title": "...", "sourceKey": "...", "currentValue": number, "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD", "reasoning": "..." }, { "type": "update_progress", "krId": "已有KR的id", "newCurrentValue": number, "reasoning": "..." }, { "type": "complete_kr", "krId": "已有KR的id", "reasoning": "..." } ], "summary": "一句话总结该仓库近期开发动态" } 如果没有需要操作的内容,返回 {"actions": [], "summary": "..."} ``` ### 防重复策略(三层保障) | 层级 | 机制 | 说明 | |------|------|------| | **Commit 级** | `aiAnalyzedCommits` 表 | 已分析的 SHA 打标记,下次同步时过滤掉,不再送给 AI | | **KR 级** | `sourceKey` 唯一性检查 | 创建前查 DB,同 Objective 下同 sourceKey 只允许一条,重复则转为 update | | **Objective 级** | 项目下已有 OKR 传入 prompt | AI 看到完整上下文,避免建议创建重复目标 | ### OKR 负责人归属 - commit 有 `userId`(通过 author-matching 映射到系统用户) - AI prompt 中包含每条 commit 的提交人信息(userId + displayName) - AI 返回 action 时必须携带 `ownerId`,即该 OKR 归哪个提交人负责 - 执行时:`create_objective` 的 `ownerId` = AI 指定的 userId,`create_kr` 同理 - 跳过 `userId` 为 null 的 commits(作者未映射到系统用户) **示例**:仓库 rtc_backend 有张三、李四两个人提交代码 - 张三的 commit 涉及"用户登录" → AI 创建 KR 归属张三 - 李四的 commit 涉及"支付模块" → AI 创建 KR 归属李四 - 同一个 Objective 下可以有不同负责人的 KR ### 时间节点(由 AI 判断) - **不再固定取当前季度**,而是由 AI 根据以下信息决定: - commit 的提交时间 - 功能的复杂度(从 commit 内容推断) - 已有 OKR 的时间范围(避免冲突) - AI 返回的 `startDate`、`endDate` 直接写入 Objective 和 KR - `period` 根据 endDate 自动推算(复用已有 `dateToPeriod()`) - Objective 创建后,其日期范围由 AI 决定,后续也可由 AI 在新的分析中调整 ### 完成判定 AI 返回 `complete_kr` action 时: 1. `currentValue = targetValue`(即 100) 2. `status = 'completed'` 3. 写 krLog:`action='completed'`, `detail='AI 根据 commit {sha} 判定完成:{reasoning}'` 4. `recalcObjectiveProgress()` 时 completed 的 KR 按 100% 计入 --- ## 第四阶段:集成到同步流程 ### 4a. 修改 `backend/src/sync/scheduler.ts` ```typescript import { analyzeCommitsForOKR } from '../services/okr-ai-sync'; // 在 syncGitea() 之后调用 giteaJob = new Cron('0 2,19 * * *', async () => { await syncGitea(); if (config.AI_ENABLED) { console.info('[SCHEDULER] AI OKR 分析开始...'); await analyzeCommitsForOKR().catch(e => console.error('[SCHEDULER] AI OKR analysis failed:', e) ); } }); ``` ### 4b. 新增 API 端点 (`backend/src/routes/okr.ts`) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/okr/ai-analyze` | 手动触发 AI 分析(admin/manager) | | POST | `/api/okr/ai-analyze/preview` | 预览模式:返回 AI 建议但不执行 | ### 4c. 修改 `backend/src/services/okr.ts` - 导出 `dateToPeriod()` 函数供 okr-ai-sync 使用 - `recalcObjectiveProgress()` 增加对 `status === 'completed'` 的处理(按 100% 计入进度) --- ## 第五阶段:DB 迁移 新增迁移文件 `backend/drizzle/0002_add_ai_okr_fields.sql`: ```sql -- 新表:增量标记(已分析过的 commit 不再重复处理) CREATE TABLE ai_analyzed_commits ( id VARCHAR(50) PRIMARY KEY, commit_sha VARCHAR(200) NOT NULL, batch_id VARCHAR(50) NOT NULL, created_at DATETIME NOT NULL, UNIQUE INDEX uniq_analyzed_sha (commit_sha) ); -- objectives 加字段 ALTER TABLE objectives ADD COLUMN source VARCHAR(50) DEFAULT 'manual'; -- key_results 加字段 ALTER TABLE key_results ADD COLUMN source VARCHAR(50) DEFAULT 'manual'; ALTER TABLE key_results ADD COLUMN source_key VARCHAR(300) NULL; -- syncLogs source 枚举扩展 ALTER TABLE sync_logs MODIFY COLUMN source ENUM('plane', 'gitea', 'ai_okr') NOT NULL; ``` --- ## 关键文件清单 | 文件 | 操作 | 说明 | |------|------|------| | `backend/src/db/schema.ts` | 修改 | 新表 + 新字段 | | `backend/src/config.ts` | 修改 | 豆包 AI 环境变量 | | `backend/src/services/llm-client.ts` | **新建** | 豆包 API 调用封装 | | `backend/src/services/okr-ai-sync.ts` | **新建** | 核心 AI 分析 + 增量更新逻辑 | | `backend/src/services/okr.ts` | 修改 | 导出 dateToPeriod, completed 状态处理 | | `backend/src/sync/scheduler.ts` | 修改 | 同步后触发 AI 分析 | | `backend/src/routes/okr.ts` | 修改 | 新增手动触发端点 | | `backend/drizzle/0002_*.sql` | **新建** | DB 迁移 | --- ## 实施顺序 1. Schema 变更 + 迁移文件(第一、五阶段) 2. config.ts 加豆包 AI 环境变量(第二阶段 2a) 3. llm-client.ts — 豆包 API 封装(第二阶段 2b) 4. okr.ts 小改(导出 dateToPeriod + completed 处理) 5. okr-ai-sync.ts 核心服务(第三阶段) 6. scheduler.ts 集成 + 新 API 端点(第四阶段) 7. 端到端测试 --- ## 验证方案 1. 配置 `.env`:`AI_ENABLED=true`,`AI_API_KEY=...`,`AI_MODEL=doubao-seed-2-0-pro-260215` 2. 确保数据库中已有项目、用户、项目-仓库绑定、git commits 数据 3. 调用 `POST /api/okr/ai-analyze/preview` 查看 AI 返回的 actions(不写库) 4. 确认 actions 合理后,调用 `POST /api/okr/ai-analyze` 执行 5. 通过 `GET /api/okr` 验证 OKR 数据已生成,时间节点由 AI 合理设定 6. 再次调用 `POST /api/okr/ai-analyze`,确认不会重复生成(增量标记生效) 7. 手动触发 Git 同步拉到新 commits,再次分析,确认只处理新增 commits 8. 检查 krLogs 中有 AI 操作记录 9. 前端 OKR 页面查看显示是否正常 --- ## 错误处理 | 场景 | 处理 | |------|------| | AI_ENABLED=false 或 API key 为空 | 静默跳过,不影响 Git 同步 | | 豆包 API 返回非 JSON | 尝试用正则提取 ```json 块,失败则记 syncLogs error | | 豆包 API 超时(60s) | 记错误日志,commits 保持"未分析"状态,下次同步重试 | | AI 建议更新不存在的 KR | 跳过该 action,记 warning | | AI 建议创建重复 sourceKey 的 KR | 转为 update_progress | | commit 的 repo 没有绑定到任何项目 | 跳过该仓库的分析 | | 某个仓库无新增 commits | 跳过,不调用 AI |