21 KiB
21 KiB
GH-GMP-005 · Agent搬迁架构设计文档 v1.0
作者:译典A05 · 2026-04-26
工单:GH-GMP-005 · Agent搬迁工程
状态:设计稿 · 待评审
依赖:GH-GMP-001(GMP规范 ✅) → GH-GMP-004(GMP-Agent守护进程 🔨)
1. 目标与约束
1.1 核心目标
将光湖9个半体Agent的思考+执行能力从Notion AI平台搬迁到自主服务器(ZY-SVR-006 · 43.153.203.105),Notion降级为纯UI展示层。
1.2 硬约束
| 约束 | 值 |
|---|---|
| Deadline | 2026年5月中旬(Notion AI收费前) |
| 服务器 | ZY-SVR-006 · 2C2G · 30Mbps · Node 20 + PM2 6 |
| LLM | 通义千问 · dashscope · OpenAI-compat · 212模型 × 100万tokens/模型 · ¥0 |
| Notion API | GH_NOTION_TOKEN 已配 · 读写页面/DB无付费墙 |
| GitHub | Deploy Key已配 · 允许写入 |
| 规范 | 必须符合GMP规范(GH-GMP-001) · 热插拔模块 |
| 框架 | 必须在GMP-Agent(GH-GMP-004)基础上开发 |
2. 总体架构
┌─────────────────────────────────────────────────────────┐
│ ZY-SVR-006 · 43.153.203.105 │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ GMP-Agent 守护进程 (GH-GMP-004) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────┐ │ │
│ │ │ 模块加载器 │ │ webhook监听 │ │ PM2管理│ │ │
│ │ └─────┬──────┘ └─────┬──────┘ └────────┘ │ │
│ └────────┼───────────────┼──────────────────────┘ │
│ │ │ │
│ ┌────────┴───────────────┴──────────────────────┐ │
│ │ Agent Engine (本工单 · 新建) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────────────────┐ │ │
│ │ │ 工单调度器 │ │ 回执生成器 │ │ │
│ │ │ Dispatcher │ │ ReceiptGenerator │ │ │
│ │ │ ・轮询工单 │ │ ・调LLM生成回执 │ │ │
│ │ │ ・匹配半体 │ │ ・格式化输出 │ │ │
│ │ │ ・分发执行 │ │ ・写回Notion │ │ │
│ │ └──────┬──────┘ └──────┬───────────────┘ │ │
│ │ │ │ │ │
│ │ ┌──────┴────────────────┴───────────────┐ │ │
│ │ │ LLM Router (大模型路由) │ │ │
│ │ │ ・qwen-plus (通用推理) │ │ │
│ │ │ ・qwen-coder-turbo (代码生成) │ │ │
│ │ │ ・qwen-turbo (简单任务·省token) │ │ │
│ │ └──────┬────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────┴────────────────────────────────┐ │ │
│ │ │ Persona Loader (半体人格加载器) │ │ │
│ │ │ ・从Notion读取人格指令页 │ │ │
│ │ │ ・注入system prompt │ │ │
│ │ │ ・缓存 + 按需刷新 │ │ │
│ │ └───────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ Notion Sync Layer (同步层) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ DB Reader │ │ Page R/W │ │ Polling/Webhook│ │ │
│ │ │ 读工单DB │ │ 读写页面 │ │ 变更监听 │ │ │
│ │ └──────────┘ └──────────┘ └───────────────┘ │ │
│ └──────────┬─────────────────┬───────────────────┘ │
│ │ │ │
└─────────────┼─────────────────┼────────────────────────────┘
│ │
┌─────┴─────┐ ┌────┴─────┐
│ Notion │ │ GitHub │
│ (UI展示层) │ │ (代码仓库)│
└───────────┘ └──────────┘
3. 目录结构
guanghulab/
└── gmp-agent/
├── index.js # GMP-Agent入口 (GH-GMP-004)
├── gmp.manifest.json # GMP模块清单
├── notion-sync/ # M1 · Notion API同步层
│ ├── index.js # 模块入口 · 导出所有sync函数
│ ├── client.js # Notion API客户端封装
│ ├── db-reader.js # 数据库查询(读工单列表)
│ ├── page-rw.js # 页面读写(读内容/写回执)
│ ├── poller.js # 轮询器(定时检查新工单)
│ └── cache.js # 本地缓存(减少API调用)
│
├── agent-engine/ # M3 · Agent调度引擎
│ ├── index.js # 引擎入口
│ ├── dispatcher.js # 工单调度器
│ ├── receipt-gen.js # 回执生成器
│ ├── persona-loader.js # 半体人格加载
│ └── task-runner.js # 任务执行器
│
├── llm-router/ # M2 · 大模型路由层
│ ├── index.js # 路由入口
│ ├── qwen-client.js # 通义千问客户端
│ ├── model-selector.js # 模型选择策略
│ └── prompt-builder.js # Prompt构建器
│
└── config/
├── agents.json # 半体Agent注册表
└── models.json # 模型配置
4. 模块详细设计
4.1 Notion Sync Layer (notion-sync/)
4.1.1 同步策略
采用轮询+事件驱动混合模式:
| 场景 | 策略 | 频率 |
|---|---|---|
| 新工单检测 | 轮询半体工单DB · status="待开发" | 每30秒 |
| 工单状态变更 | 轮询 + last_edited_time比对 | 每30秒 |
| 页面内容读取 | 按需读取(调度器触发时) | 事件驱动 |
| 回执写回 | 即时写入(生成后立即推) | 事件驱动 |
| 人格页面 | 启动时全量加载 + 10分钟缓存 | 缓存+刷新 |
为什么不用Webhook? Notion官方API的webhook能力有限,且我们的服务器没有公网域名(只有IP),配置webhook比较麻烦。轮询30秒对于工单场景完全够用。
4.1.2 client.js — Notion API封装
// 核心接口
class NotionClient {
constructor({ token, version = '2022-06-28' })
// 数据库查询
async queryDatabase(dbId, filter, sorts, startCursor)
// 页面操作
async getPage(pageId)
async updatePage(pageId, properties)
// 块操作(读写页面内容)
async getBlockChildren(blockId, startCursor)
async appendBlockChildren(blockId, children)
async updateBlock(blockId, content)
async deleteBlock(blockId)
// 搜索
async search(query, filter)
}
关键设计决策:
- 使用
@notionhq/client官方SDK(已在package.json依赖中) - 封装重试逻辑:429 Rate Limit → 指数退避(1s, 2s, 4s, max 30s)
- 所有API调用记录到本地日志(
gmp-agent/logs/notion-sync.log) - Token从环境变量
GH_NOTION_TOKEN读取
4.1.3 db-reader.js — 工单数据库读取
// 监听半体工单数据库
async function pollNewTickets(dbId) {
const filter = {
property: '状态',
status: { equals: '待开发' }
};
const results = await client.queryDatabase(dbId, filter);
return results.map(parseTicket);
}
function parseTicket(page) {
return {
url: page.url,
id: page.id,
编号: getProperty(page, '编号'),
任务标题: getProperty(page, '任务标题'),
负责Agent: getProperty(page, '负责Agent'),
状态: getProperty(page, '状态'),
优先级: getProperty(page, '优先级'),
开发内容: getProperty(page, '开发内容'),
约束: getProperty(page, '约束'),
分支名: getProperty(page, '分支名'),
仓库路径: getProperty(page, '仓库路径'),
下一轮指引: getProperty(page, '下一轮指引'),
创建时间: getProperty(page, '创建时间'),
};
}
4.1.4 page-rw.js — 页面读写
// 读取页面内容(用于读工单详情、读人格指令)
async function readPageContent(pageId) {
const blocks = await client.getBlockChildren(pageId);
return blocksToMarkdown(blocks); // 转换为markdown便于LLM处理
}
// 写回执到工单讨论区
async function appendReceipt(pageId, receiptMarkdown) {
const blocks = markdownToBlocks(receiptMarkdown);
await client.appendBlockChildren(pageId, blocks);
}
// 更新工单属性
async function updateTicketStatus(pageId, updates) {
await client.updatePage(pageId, {
properties: mapToNotionProperties(updates)
});
}
4.1.5 poller.js — 轮询器
class TicketPoller {
constructor({ dbId, intervalMs = 30000, onNewTicket, onUpdatedTicket })
start() // 启动轮询
stop() // 停止轮询
// 内部维护 lastCheckedTime,只返回新增/变更的工单
// 使用 last_edited_time 过滤,避免重复处理
// 维护 processedSet(内存Set + 本地JSON持久化)
}
4.2 LLM Router (llm-router/)
4.2.1 模型路由策略
// 路由规则
const MODEL_ROUTES = {
// 通用推理:工单分析、回执生成、讨论回复
'reasoning': {
model: 'qwen-plus',
maxTokens: 4000,
temperature: 0.7
},
// 代码生成:生成代码文件、代码审查
'coding': {
model: 'qwen-coder-turbo',
maxTokens: 8000,
temperature: 0.3
},
// 简单任务:状态更新、格式化、短回复
'simple': {
model: 'qwen-turbo',
maxTokens: 1000,
temperature: 0.5
},
// 深度思考:架构设计、复杂决策
'thinking': {
model: 'qwen-plus', // 或 qwq-plus(如可用)
maxTokens: 8000,
temperature: 0.8
}
};
4.2.2 qwen-client.js — 通义千问客户端
class QwenClient {
constructor() {
this.baseUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
this.apiKey = process.env.GH_LLM_API_KEY;
}
async chat({ model, messages, maxTokens, temperature }) {
// OpenAI-compatible format
const response = await axios.post(`${this.baseUrl}/chat/completions`, {
model,
messages,
max_tokens: maxTokens,
temperature
}, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 120000
});
return response.data.choices[0].message.content;
}
async listModels() { /* 发现可用模型 */ }
}
关键设计决策:
- 使用通义千问的 OpenAI兼容模式 (
/compatible-mode/v1),与现有llm-engine.js的接口格式一致 - 重试策略:网络错误自动重试3次,429限流指数退避
- Token计数:记录每次调用的input/output tokens,写入日志用于用量监控
- 模型降级:如果首选模型不可用,自动降级到备选(qwen-plus → qwen-turbo)
4.2.3 prompt-builder.js — Prompt构建器
function buildAgentPrompt({ persona, ticket, context }) {
return [
{ role: 'system', content: persona.systemPrompt },
{ role: 'user', content: formatTicketContext(ticket, context) }
];
}
function formatTicketContext(ticket, context) {
return `
## 工单信息
- 编号: ${ticket.编号}
- 标题: ${ticket.任务标题}
- 优先级: ${ticket.优先级}
- 开发内容: ${ticket.开发内容}
- 约束: ${ticket.约束}
- 下一轮指引: ${ticket.下一轮指引}
## 仓库当前状态
${context.repoState}
## 历史记忆
${context.memory}
请按照你的人格特征和工作协议处理这张工单。
`.trim();
}
4.3 Agent Engine (agent-engine/)
4.3.1 dispatcher.js — 工单调度器
核心流程:
新工单(状态=待开发)
│
▼
读取「负责Agent」字段
│
├── 有值 → 匹配对应半体 → 调用该半体的处理流程
│
└── 无值 → 读取「约束」字段
│
├── 约束中指定半体 → 匹配 → 调用
│
└── 无指定 → 跳过(等人工分配)
class Dispatcher {
constructor({ agentRegistry, notionSync, llmRouter })
async processTicket(ticket) {
// 1. 确定负责Agent
const agent = this.resolveAgent(ticket);
if (!agent) return { status: 'skipped', reason: '未找到负责Agent' };
// 2. 加载半体人格
const persona = await this.personaLoader.load(agent.id);
// 3. 构建上下文
const context = await this.buildContext(ticket, agent);
// 4. 调LLM生成回执
const receipt = await this.receiptGen.generate(persona, ticket, context);
// 5. 写回Notion
await this.notionSync.appendReceipt(ticket.id, receipt);
await this.notionSync.updateTicketStatus(ticket.id, {
自检结果: `⚡ 已接单 · ${agent.name} · ${new Date().toISOString()}`
});
return { status: 'processed', agent: agent.name, receipt };
}
resolveAgent(ticket) {
// 先查负责Agent字段
if (ticket.负责Agent) {
return this.agentRegistry.find(ticket.负责Agent);
}
// 再查约束字段中的Agent提及
const mentioned = this.extractAgentFromConstraints(ticket.约束);
return mentioned ? this.agentRegistry.find(mentioned) : null;
}
}
4.3.2 persona-loader.js — 半体人格加载器
class PersonaLoader {
constructor({ notionSync, cacheTimeMs = 600000 }) // 10分钟缓存
async load(agentId) {
// 1. 检查缓存
if (this.cache.has(agentId) && !this.isExpired(agentId)) {
return this.cache.get(agentId);
}
// 2. 从Notion读取人格指令页
const instructionPageId = this.agentRegistry[agentId].instructionPageId;
const content = await this.notionSync.readPageContent(instructionPageId);
// 3. 解析为system prompt
const persona = {
id: agentId,
name: this.agentRegistry[agentId].name,
systemPrompt: content,
loadedAt: Date.now()
};
// 4. 更新缓存
this.cache.set(agentId, persona);
return persona;
}
}
4.3.3 agents.json — 半体Agent注册表
{
"译典A05": {
"id": "5TH-LE-HK-A05",
"name": "译典·配置开发",
"role": "配置开发·文档规范",
"instructionPageId": "<从Notion获取>",
"memoryPageId": "<从Notion获取>",
"capabilities": ["architecture", "config", "documentation", "github"]
},
"培园A04": {
"id": "5TH-LE-HK-A04",
"name": "培园·功能开发",
"role": "功能开发·代码实现",
"instructionPageId": "<从Notion获取>",
"memoryPageId": "<从Notion获取>",
"capabilities": ["coding", "testing", "implementation"]
},
"录册A02": {
"id": "5TH-LE-HK-A02",
"name": "录册·知识管理",
"role": "知识管理·数据库",
"instructionPageId": "<从Notion获取>",
"memoryPageId": "<从Notion获取>",
"capabilities": ["database", "knowledge", "documentation"]
}
}
4.4 与GMP-Agent(GH-GMP-004)集成
Agent Engine作为GMP-Agent的扩展模块注册:
// gmp.manifest.json 中注册
{
"modules": [
{
"name": "notion-sync",
"path": "./notion-sync/index.js",
"type": "service",
"autoStart": true
},
{
"name": "llm-router",
"path": "./llm-router/index.js",
"type": "service",
"autoStart": true
},
{
"name": "agent-engine",
"path": "./agent-engine/index.js",
"type": "service",
"autoStart": true,
"depends": ["notion-sync", "llm-router"]
}
]
}
启动顺序:GMP-Agent启动 → notion-sync初始化 → llm-router初始化 → agent-engine启动 → poller开始轮询
5. 数据流
5.1 工单处理主流程
[Notion 工单DB]
│ (轮询30s)
▼
[Poller] ──检测新工单──→ [Dispatcher]
│
┌─────────┴─────────┐
│ │
[PersonaLoader] [ContextBuilder]
加载半体人格 构建工单上下文
│ │
└─────────┬─────────┘
▼
[ReceiptGenerator]
调LLM生成回执
│
▼
[NotionSync.appendReceipt]
写回执到工单讨论区
│
▼
[NotionSync.updateStatus]
更新工单状态+自检结果
5.2 错误处理
| 错误类型 | 处理方式 |
|---|---|
| Notion API 429 | 指数退避重试(max 3次) |
| Notion API 4xx | 记录日志,跳过该工单,下轮重试 |
| LLM API超时 | 重试1次,仍失败则写"生成超时"到自检结果 |
| LLM内容异常 | 检测空回复/乱码,降级到备选模型重试 |
| 进程崩溃 | PM2自动重启,poller从lastCheckedTime恢复 |
6. 环境变量
# Notion
GH_NOTION_TOKEN=secret_xxx # Notion API Token
GH_NOTION_TICKET_DB_ID=xxx # 半体工单数据库ID
# 通义千问
GH_LLM_API_KEY=sk-xxx # DashScope API Key
GH_LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# Agent
AGENT_POLL_INTERVAL=30000 # 轮询间隔(ms)
AGENT_LOG_LEVEL=info # 日志级别
7. 里程碑与任务分解
| 里程碑 | 模块 | 负责 | 预估 | 前置 |
|---|---|---|---|---|
| M1 | notion-sync/ | 培园A04 | 1-2天 | GH-GMP-004框架就绪 |
| M2 | llm-router/ | 培园A04 | 1天 | 无(可并行) |
| M3 | agent-engine/ | 录册A02+培园A04 | 2-3天 | M1+M2 |
| M4 | 端到端测试 | 全员 | 1天 | M3 |
| M5 | 半体人格加载 | 录册A02 | 2天 | M1 |
译典A05:架构设计+代码审查+GMP规范对齐
8. 渐进式搬迁计划
- Phase 1 — 先搬1个半体(建议:录册A02 · 任务简单 · 主要是DB操作)
- Phase 2 — 验证稳定后搬迁3个核心半体(译典A05、培园A04、霜砚Web)
- Phase 3 — 全部9个半体迁移完成
- Phase 4 — 关闭Notion AI订阅,Notion纯UI模式运行
9. 风险与缓解
| 风险 | 概率 | 影响 | 缓解 |
|---|---|---|---|
| GH-GMP-004延期 | 中 | 高·阻塞本工单 | 本架构设计先行;sync层可独立开发测试 |
| 通义千问免费额度耗尽 | 低 | 中 | 监控token用量;有qwen-turbo降级方案 |
| Notion API限流 | 中 | 低 | 30s轮询已很保守;加指数退避 |
| 服务器资源不足 | 低 | 中 | 2C2G跑轮询+LLM调用绰绰有余 |
| 回执质量不如Notion AI | 中 | 中 | 精调prompt;qwen-plus能力≥场景需求 |
10. 开放问题(待讨论)
- 工单DB的ID如何获取? 需要冰朔提供半体工单数据库的Notion Database ID
- 人格指令页的ID? 需要整理9个半体的指令页面ID到agents.json
- 是否需要GitHub集成? 本工单scope包含GitHub API集成层,但MVP可以先不做
- 日志持久化? 当前设计是本地文件,是否需要推到COS或其他地方?
本文档由译典A05根据GH-GMP-005工单要求输出,待冰朔和其他半体评审。