feat(phase6-9): Complete modular architecture and Agent Teams
Phase 6.4 - Modular Architecture: - Add lewooogo-brain adapters for LLM providers - Add lewooogo-data dual memory (Redis + PostgreSQL) - Implement consensus engine for multi-agent decisions - Add incident memory service for historical context Phase 9 - Agent Teams (Claude Agent SDK): - Add base agent class with Claude Sonnet 4 integration - Implement action planner, blast radius, and security agents - Add agent API endpoints and proposal workflow - Integrate ADR-009 OpenClaw Agent Teams architecture DevOps & CI/CD: - Add GitHub Actions CI/CD workflows (ci.yaml, cd.yaml) - Add pre-commit hooks and secrets baseline - Add docker-compose for local development - Update Kubernetes network policies Frontend Improvements: - Add auto-healing error boundary component - Update i18n messages for agent features - Enhance dual-state incident card with execution feedback Documentation: - Add 7 ADRs covering MCP, design system, architecture decisions - Update ARCHITECTURE_MEMORY.md with modular design - Add GLOBAL_RULES.md and SOUL.md for project identity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
130
docs/adr/ADR-001-mcp-protocol-adoption.md
Normal file
130
docs/adr/ADR-001-mcp-protocol-adoption.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# ADR-001: MCP Protocol 採用
|
||||
|
||||
> **狀態**: Accepted
|
||||
> **日期**: 2026-03-19
|
||||
> **決策者**: CTO + CEO
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 的 leWOOOgo Engine 需要與大量外部工具整合 (K8s, SSH, AWS/GCP, Database, Notification 等)。傳統做法是針對每個服務寫專屬 Adapter,耗時且難以維護。
|
||||
|
||||
Anthropic 的 **Model Context Protocol (MCP)** 提供標準化的 AI-Tool 溝通協議,已有數百個社群 MCP Server 可直接使用。
|
||||
|
||||
## 決策
|
||||
|
||||
**採用 MCP 作為 leWOOOgo BRAIN ↔ ACTION 的標準通訊協議**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ leWOOOgo Engine │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 🧱 INPUT ──→ 🧠 BRAIN ──→ 📢 OUTPUT │
|
||||
│ │ │
|
||||
│ ↓ (MCP Protocol) │
|
||||
│ 🔧 ACTION ←→ [MCP Servers] │
|
||||
│ │ │
|
||||
│ ↓ │
|
||||
│ 📊 DATA │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### MCP Server 分類
|
||||
|
||||
| 類別 | 範例 MCP Server | 用途 |
|
||||
|------|----------------|------|
|
||||
| **Infrastructure** | kubernetes, docker, ssh | 基礎設施操作 |
|
||||
| **Cloud** | aws, gcp, azure | 雲端資源管理 |
|
||||
| **Database** | postgres, redis, mongodb | 資料存取 |
|
||||
| **Notification** | slack, telegram, email | 訊息發送 |
|
||||
| **Monitoring** | prometheus, grafana | 監控查詢 |
|
||||
| **Security** | vault, trivy | 安全掃描 |
|
||||
|
||||
### leWOOOgo 整合方式
|
||||
|
||||
```typescript
|
||||
// packages/lewooogo-brain/src/mcp-bridge.ts
|
||||
|
||||
interface MCPBridge {
|
||||
// 動態載入 MCP Server
|
||||
loadServer(serverName: string): Promise<MCPServer>
|
||||
|
||||
// 執行 MCP Tool
|
||||
callTool(server: string, tool: string, params: object): Promise<MCPResult>
|
||||
|
||||
// 列出可用工具
|
||||
listTools(server: string): Promise<MCPToolDefinition[]>
|
||||
}
|
||||
```
|
||||
|
||||
## 理由
|
||||
|
||||
### 1. 生態系統成熟
|
||||
|
||||
| 指標 | 數值 |
|
||||
|------|------|
|
||||
| 社群 MCP Server | 300+ |
|
||||
| 官方維護 Server | 20+ |
|
||||
| 協議版本 | Stable (2024-11) |
|
||||
|
||||
### 2. 與 Claude 深度整合
|
||||
|
||||
AWOOOI 使用 Claude 作為主要 LLM,MCP 是 Anthropic 原生協議,整合最順暢。
|
||||
|
||||
### 3. 節省開發時間
|
||||
|
||||
| 方案 | 預估工時 |
|
||||
|------|---------|
|
||||
| 自建 50 個 Adapter | 500+ 小時 |
|
||||
| 採用 MCP + 自訂 5 個 | 50 小時 |
|
||||
|
||||
### 4. 標準化介面
|
||||
|
||||
所有工具使用相同的 JSON-RPC 介面,簡化 BRAIN 邏輯。
|
||||
|
||||
## 後果
|
||||
|
||||
### 優點
|
||||
|
||||
- **即時獲得** 數百種工具能力
|
||||
- **社群維護** 減輕維護負擔
|
||||
- **標準協議** 簡化架構設計
|
||||
- **Claude 原生** 最佳 LLM 整合體驗
|
||||
|
||||
### 缺點
|
||||
|
||||
- **依賴外部** 需信任社群 MCP Server 品質
|
||||
- **協議鎖定** 若 MCP 標準改變需跟進
|
||||
|
||||
### 風險
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| MCP Server 品質不一 | 建立內部審核清單,只允許白名單 Server |
|
||||
| 安全漏洞 | 所有 MCP 調用經過 Privacy Shield 脫敏 |
|
||||
| 效能瓶頸 | 關鍵路徑自建 Adapter,非關鍵走 MCP |
|
||||
|
||||
### 例外情況
|
||||
|
||||
以下場景**不使用**社群 MCP Server,改自建 leWOOOgo Adapter:
|
||||
|
||||
1. **核心業務邏輯** - 如 ClawBot Triage Engine
|
||||
2. **高頻調用** - 如 Redis Cache (效能考量)
|
||||
3. **機敏操作** - 如 K8s Delete (需額外授權)
|
||||
|
||||
## 實施計畫
|
||||
|
||||
| Phase | 任務 | 時程 |
|
||||
|-------|------|------|
|
||||
| 0 | 定義 MCPBridge 介面 | Week 1 |
|
||||
| 1 | 整合 5 個核心 MCP Server | Week 2-3 |
|
||||
| 2 | 建立 MCP Server 白名單機制 | Week 3 |
|
||||
| 3 | Privacy Shield 整合 | Week 4 |
|
||||
|
||||
## 參考
|
||||
|
||||
- [MCP Official Spec](https://spec.modelcontextprotocol.io/)
|
||||
- [MCP Server Registry](https://github.com/modelcontextprotocol/servers)
|
||||
- [Anthropic MCP Announcement](https://www.anthropic.com/news/model-context-protocol)
|
||||
- 會議記錄: `docs/meetings/2026-03-19_FRONTEND_RESTRUCTURE_STRATEGY.md`
|
||||
191
docs/adr/ADR-002-nothing-tech-design-system.md
Normal file
191
docs/adr/ADR-002-nothing-tech-design-system.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# ADR-002: Nothing.tech 設計系統採用
|
||||
|
||||
> **狀態**: Accepted
|
||||
> **日期**: 2026-03-19
|
||||
> **決策者**: CPO + CTO
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 需要統一的視覺語言,區隔於傳統 Dashboard 風格。CEO 在戰略會議中指定採用 **Nothing.tech** 風格:點陣字體 + 毛玻璃效果 + 極簡黑白。
|
||||
|
||||
此風格強調「科技感」與「未來感」,符合 AI-First 運維平台定位。
|
||||
|
||||
## 決策
|
||||
|
||||
**採用 Nothing.tech 風格作為 AWOOOI 設計系統基礎**
|
||||
|
||||
### 色彩系統
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 主色 */
|
||||
--nothing-black: #000000;
|
||||
--nothing-white: #FFFFFF;
|
||||
--nothing-red: #D71921; /* 告警、錯誤、Critical */
|
||||
|
||||
/* 灰階 */
|
||||
--nothing-gray-50: #FAFAFA;
|
||||
--nothing-gray-100: #F5F5F5;
|
||||
--nothing-gray-200: #E5E5E5;
|
||||
--nothing-gray-300: #D4D4D4;
|
||||
--nothing-gray-400: #A3A3A3;
|
||||
--nothing-gray-500: #737373;
|
||||
--nothing-gray-600: #525252;
|
||||
--nothing-gray-700: #404040;
|
||||
--nothing-gray-800: #1A1A1A;
|
||||
--nothing-gray-900: #0A0A0A;
|
||||
|
||||
/* 語意色 */
|
||||
--status-healthy: #22C55E; /* Green - 正常 */
|
||||
--status-warning: #F59E0B; /* Amber - 警告 */
|
||||
--status-critical: #D71921; /* Nothing Red - 嚴重 */
|
||||
--status-unknown: #6B7280; /* Gray - 未知 */
|
||||
}
|
||||
```
|
||||
|
||||
### 字體系統
|
||||
|
||||
| 用途 | 字體 | Fallback |
|
||||
|------|------|----------|
|
||||
| **AI 介面** | NDot 57 | JetBrains Mono, monospace |
|
||||
| **標題** | NDot 47 | Inter, system-ui |
|
||||
| **內文** | Inter | system-ui, sans-serif |
|
||||
| **程式碼** | JetBrains Mono | Fira Code, monospace |
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-display: "NDot", "JetBrains Mono", monospace;
|
||||
--font-heading: "NDot", "Inter", system-ui;
|
||||
--font-body: "Inter", system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
}
|
||||
```
|
||||
|
||||
### 毛玻璃效果 (Glassmorphism)
|
||||
|
||||
```css
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.glass-panel-dark {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
```
|
||||
|
||||
### 動效規範
|
||||
|
||||
| 效果 | 用途 | Duration |
|
||||
|------|------|----------|
|
||||
| **呼吸燈** | AI 狀態指示 | 2s ease-in-out |
|
||||
| **打字機** | ClawBot 回應 | 30ms/字元 |
|
||||
| **淡入** | 卡片載入 | 200ms ease-out |
|
||||
| **滑入** | 側邊欄 | 300ms cubic-bezier |
|
||||
|
||||
```css
|
||||
@keyframes breathe {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.ai-status-indicator {
|
||||
animation: breathe 2s ease-in-out infinite;
|
||||
}
|
||||
```
|
||||
|
||||
## 理由
|
||||
|
||||
### 1. 品牌差異化
|
||||
|
||||
傳統運維 Dashboard 使用 Material/Ant Design,視覺同質化嚴重。Nothing.tech 風格能立即建立品牌辨識度。
|
||||
|
||||
### 2. AI-First 視覺語言
|
||||
|
||||
點陣字體與極簡風格傳達「精準」與「科技感」,符合 AI 運維平台定位。
|
||||
|
||||
### 3. 技術可行性
|
||||
|
||||
| 需求 | 實現方式 |
|
||||
|------|---------|
|
||||
| 點陣字體 | NDot (需購買) 或 Dot Matrix (免費替代) |
|
||||
| 毛玻璃 | CSS backdrop-filter (現代瀏覽器支援) |
|
||||
| 深色主題 | Tailwind dark mode |
|
||||
|
||||
## 後果
|
||||
|
||||
### 優點
|
||||
|
||||
- **品牌辨識度** 強烈視覺風格
|
||||
- **AI 定位** 符合 Agent-Centric 理念
|
||||
- **現代感** 吸引科技用戶
|
||||
|
||||
### 缺點
|
||||
|
||||
- **字體成本** NDot 需商業授權
|
||||
- **相容性** 舊瀏覽器不支援 backdrop-filter
|
||||
|
||||
### 風險
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| NDot 授權費用 | 初期用 JetBrains Mono 替代,驗證後再購買 |
|
||||
| Safari 毛玻璃問題 | 加入 `-webkit-backdrop-filter` prefix |
|
||||
| 可讀性 | 限制點陣字體於標題,內文用 Inter |
|
||||
|
||||
## Tailwind 配置
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
nothing: {
|
||||
black: '#000000',
|
||||
white: '#FFFFFF',
|
||||
red: '#D71921',
|
||||
gray: {
|
||||
50: '#FAFAFA',
|
||||
100: '#F5F5F5',
|
||||
200: '#E5E5E5',
|
||||
300: '#D4D4D4',
|
||||
400: '#A3A3A3',
|
||||
500: '#737373',
|
||||
600: '#525252',
|
||||
700: '#404040',
|
||||
800: '#1A1A1A',
|
||||
900: '#0A0A0A',
|
||||
}
|
||||
},
|
||||
status: {
|
||||
healthy: '#22C55E',
|
||||
warning: '#F59E0B',
|
||||
critical: '#D71921',
|
||||
unknown: '#6B7280',
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
display: ['NDot', 'JetBrains Mono', 'monospace'],
|
||||
heading: ['NDot', 'Inter', 'system-ui'],
|
||||
body: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
||||
},
|
||||
backdropBlur: {
|
||||
glass: '20px',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 參考
|
||||
|
||||
- [Nothing.tech Official](https://nothing.tech/)
|
||||
- [NDot Font](https://pangrampangram.com/products/ndot)
|
||||
- 會議記錄: `docs/meetings/2026-03-19_FRONTEND_RESTRUCTURE_STRATEGY.md`
|
||||
244
docs/adr/ADR-003-lewooogo-module-architecture.md
Normal file
244
docs/adr/ADR-003-lewooogo-module-architecture.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# ADR-003: leWOOOgo 模組化架構
|
||||
|
||||
> **狀態**: Accepted
|
||||
> **日期**: 2026-03-19
|
||||
> **決策者**: CTO + CEO
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 需要高度模組化的架構,讓開發者能像組樂高一樣快速組合功能。CEO 命名此引擎為 **leWOOOgo** (樂高 + WOOO)。
|
||||
|
||||
傳統 monolithic 架構難以擴展,plugin 架構則能支援生態系統發展。
|
||||
|
||||
## 決策
|
||||
|
||||
**採用六大積木類別的 Plugin 架構**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ leWOOOgo Engine │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 🧱 INPUT ──────→ 🧠 BRAIN ──────→ 📢 OUTPUT │
|
||||
│ (觸發器) (AI 處理) (通知) │
|
||||
│ │ │ │ │
|
||||
│ │ ↓ │ │
|
||||
│ │ 🔧 ACTION │ │
|
||||
│ │ (執行器) │ │
|
||||
│ │ │ │ │
|
||||
│ └───────→ 📊 DATA ←───────────────┘ │
|
||||
│ (儲存) │
|
||||
│ │
|
||||
│ 🎨 UI │
|
||||
│ (介面元件) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 六大積木類別
|
||||
|
||||
| 類別 | 介面 | 用途 | 範例 |
|
||||
|------|------|------|------|
|
||||
| **INPUT** | `TriggerPlugin` | 觸發工作流 | Webhook, Cron, Alert, Email |
|
||||
| **BRAIN** | `AgentProvider` | AI 處理決策 | LLM Router, RAG, Triage, MCP |
|
||||
| **OUTPUT** | `NotificationChannel` | 發送通知 | Telegram, Slack, LINE, Email |
|
||||
| **ACTION** | `ActionExecutor` | 執行操作 | K8s, SSH, Docker, API Call |
|
||||
| **DATA** | `DataAdapter` | 資料存取 | PostgreSQL, Redis, S3, Vector |
|
||||
| **UI** | `WidgetComponent` | 介面元件 | Card, Chart, Timeline, Status |
|
||||
|
||||
### 核心介面定義
|
||||
|
||||
```typescript
|
||||
// packages/lewooogo-core/src/interfaces/plugin.ts
|
||||
|
||||
/** 所有 Plugin 的基礎介面 */
|
||||
interface LeWOOOgoPlugin {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly version: string
|
||||
readonly category: 'INPUT' | 'BRAIN' | 'OUTPUT' | 'ACTION' | 'DATA' | 'UI'
|
||||
|
||||
initialize(): Promise<void>
|
||||
healthCheck(): Promise<HealthStatus>
|
||||
shutdown(): Promise<void>
|
||||
}
|
||||
|
||||
/** INPUT 觸發器 */
|
||||
interface TriggerPlugin extends LeWOOOgoPlugin {
|
||||
category: 'INPUT'
|
||||
subscribe(handler: TriggerHandler): Unsubscribe
|
||||
getSchema(): TriggerSchema
|
||||
}
|
||||
|
||||
/** BRAIN AI 處理器 */
|
||||
interface AgentProvider extends LeWOOOgoPlugin {
|
||||
category: 'BRAIN'
|
||||
process(input: AgentInput): Promise<AgentOutput>
|
||||
getCapabilities(): AgentCapability[]
|
||||
}
|
||||
|
||||
/** OUTPUT 通知頻道 */
|
||||
interface NotificationChannel extends LeWOOOgoPlugin {
|
||||
category: 'OUTPUT'
|
||||
send(message: NotificationMessage): Promise<SendResult>
|
||||
getTemplates(): NotificationTemplate[]
|
||||
}
|
||||
|
||||
/** ACTION 執行器 */
|
||||
interface ActionExecutor extends LeWOOOgoPlugin {
|
||||
category: 'ACTION'
|
||||
execute(action: ActionRequest): Promise<ActionResult>
|
||||
dryRun(action: ActionRequest): Promise<DryRunResult>
|
||||
rollback(executionId: string): Promise<RollbackResult>
|
||||
}
|
||||
|
||||
/** DATA 資料適配器 */
|
||||
interface DataAdapter extends LeWOOOgoPlugin {
|
||||
category: 'DATA'
|
||||
connect(): Promise<void>
|
||||
query<T>(request: QueryRequest): Promise<T>
|
||||
disconnect(): Promise<void>
|
||||
}
|
||||
|
||||
/** UI 介面元件 */
|
||||
interface WidgetComponent extends LeWOOOgoPlugin {
|
||||
category: 'UI'
|
||||
render(props: WidgetProps): ReactNode
|
||||
getConfigSchema(): JSONSchema
|
||||
}
|
||||
```
|
||||
|
||||
### 資料夾結構
|
||||
|
||||
```
|
||||
packages/
|
||||
├── lewooogo-core/ # 核心引擎
|
||||
│ ├── src/
|
||||
│ │ ├── interfaces/ # 六大介面定義
|
||||
│ │ ├── registry/ # Plugin 註冊中心
|
||||
│ │ ├── pipeline/ # 工作流引擎
|
||||
│ │ └── utils/ # 共用工具
|
||||
│ └── package.json
|
||||
│
|
||||
├── lewooogo-input/ # INPUT 積木
|
||||
│ ├── src/
|
||||
│ │ ├── webhook/
|
||||
│ │ ├── cron/
|
||||
│ │ ├── prometheus-alert/
|
||||
│ │ └── email-trigger/
|
||||
│ └── package.json
|
||||
│
|
||||
├── lewooogo-brain/ # BRAIN 積木
|
||||
│ ├── src/
|
||||
│ │ ├── llm-router/ # LLM 路由器
|
||||
│ │ ├── mcp-bridge/ # MCP 整合 (ADR-001)
|
||||
│ │ ├── triage-engine/ # 告警分級
|
||||
│ │ └── rag-provider/ # RAG 檢索
|
||||
│ └── package.json
|
||||
│
|
||||
├── lewooogo-output/ # OUTPUT 積木
|
||||
│ ├── src/
|
||||
│ │ ├── telegram/
|
||||
│ │ ├── slack/
|
||||
│ │ ├── line/
|
||||
│ │ └── email/
|
||||
│ └── package.json
|
||||
│
|
||||
├── lewooogo-action/ # ACTION 積木
|
||||
│ ├── src/
|
||||
│ │ ├── kubernetes/
|
||||
│ │ ├── ssh/
|
||||
│ │ ├── docker/
|
||||
│ │ └── http-api/
|
||||
│ └── package.json
|
||||
│
|
||||
├── lewooogo-data/ # DATA 積木
|
||||
│ ├── src/
|
||||
│ │ ├── postgres/
|
||||
│ │ ├── redis/
|
||||
│ │ ├── s3/
|
||||
│ │ └── vector-db/
|
||||
│ └── package.json
|
||||
│
|
||||
└── lewooogo-ui/ # UI 積木
|
||||
├── src/
|
||||
│ ├── cards/
|
||||
│ ├── charts/
|
||||
│ ├── timeline/
|
||||
│ └── status-indicators/
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## 理由
|
||||
|
||||
### 1. 開發者體驗 (DX)
|
||||
|
||||
| 傳統方式 | leWOOOgo 方式 |
|
||||
|---------|--------------|
|
||||
| 修改核心程式碼 | npm install + 註冊 |
|
||||
| 重新部署整體 | 熱插拔 Plugin |
|
||||
| 閱讀大量文檔 | 統一介面 + TypeScript |
|
||||
|
||||
### 2. 生態系統潛力
|
||||
|
||||
標準介面允許第三方開發 Plugin,形成市場。
|
||||
|
||||
### 3. 測試隔離
|
||||
|
||||
每個 Plugin 獨立測試,不影響核心引擎。
|
||||
|
||||
## 後果
|
||||
|
||||
### 優點
|
||||
|
||||
- **模組化** 功能獨立開發部署
|
||||
- **可擴展** 第三方生態系統
|
||||
- **可測試** 單元測試隔離
|
||||
- **可維護** 責任分離清晰
|
||||
|
||||
### 缺點
|
||||
|
||||
- **初期成本** 需建立完整介面規範
|
||||
- **效能開銷** Plugin 動態載入有成本
|
||||
- **版本管理** 多 package 需 monorepo 工具
|
||||
|
||||
### 風險
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| 介面設計錯誤 | Phase 0 充分討論 + 早期 POC 驗證 |
|
||||
| Plugin 衝突 | Plugin Registry 管理 + 命名空間隔離 |
|
||||
| 效能問題 | 關鍵路徑避免過度抽象,效能測試 |
|
||||
|
||||
## Monorepo 工具
|
||||
|
||||
採用 **pnpm workspace** + **Turborepo**:
|
||||
|
||||
```yaml
|
||||
# pnpm-workspace.yaml
|
||||
packages:
|
||||
- 'apps/*'
|
||||
- 'packages/*'
|
||||
```
|
||||
|
||||
```json
|
||||
// turbo.json
|
||||
{
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["build"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 參考
|
||||
|
||||
- [Turborepo](https://turbo.build/)
|
||||
- [pnpm Workspaces](https://pnpm.io/workspaces)
|
||||
- ADR-001: MCP Protocol 採用
|
||||
- 會議記錄: `docs/meetings/2026-03-19_FRONTEND_RESTRUCTURE_STRATEGY.md`
|
||||
268
docs/adr/ADR-004-state-management.md
Normal file
268
docs/adr/ADR-004-state-management.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# ADR-004: 前端狀態管理統一採用 Zustand
|
||||
|
||||
> **狀態**: Accepted
|
||||
> **日期**: 2026-03-19
|
||||
> **更新日期**: 2026-03-20 (Gate 0 驗證完成)
|
||||
> **決策者**: CTO + CPO
|
||||
|
||||
---
|
||||
|
||||
## Gate 0 里程碑驗證 (2026-03-20)
|
||||
|
||||
**Tracer Bullet 測試通過!** 以下實作已驗證:
|
||||
|
||||
| 元件 | Store | 狀態 |
|
||||
|------|-------|------|
|
||||
| Dashboard SSE | `dashboard.store.ts` | ✅ 即時同步 |
|
||||
| Approval Multi-Sig | `approval.store.ts` | ✅ 狀態機運作正常 |
|
||||
| HITL 簽核流程 | 整合 API `/approvals/{id}/approve` | ✅ TOCTOU 防護驗證 |
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 的前端 (Agent Hub) 需要處理高度頻繁的狀態更新,包括:
|
||||
- ClawBot 的 SSE 思考串流 (`/agent/thinking`)
|
||||
- 即時狀態燈 (Data Pincer 呼吸動畫)
|
||||
- 待授權卡片的佇列管理 (`/approvals`)
|
||||
- Plugin 健康狀態即時更新
|
||||
|
||||
我們需要一個輕量、無需過度樣板代碼 (Boilerplate),且能與 React 18 完美協作的狀態管理庫。
|
||||
|
||||
## 決策
|
||||
|
||||
**全面採用 Zustand 作為全域狀態管理工具**
|
||||
|
||||
```typescript
|
||||
// stores/agent.store.ts
|
||||
import { create } from 'zustand'
|
||||
import { subscribeWithSelector } from 'zustand/middleware'
|
||||
|
||||
interface AgentState {
|
||||
status: 'idle' | 'thinking' | 'executing' | 'waiting_approval'
|
||||
thinkingStream: string[]
|
||||
pendingApprovals: Approval[]
|
||||
|
||||
// Actions
|
||||
setStatus: (status: AgentState['status']) => void
|
||||
appendThinking: (chunk: string) => void
|
||||
addApproval: (approval: Approval) => void
|
||||
}
|
||||
|
||||
export const useAgentStore = create<AgentState>()(
|
||||
subscribeWithSelector((set) => ({
|
||||
status: 'idle',
|
||||
thinkingStream: [],
|
||||
pendingApprovals: [],
|
||||
|
||||
setStatus: (status) => set({ status }),
|
||||
appendThinking: (chunk) => set((s) => ({
|
||||
thinkingStream: [...s.thinkingStream, chunk]
|
||||
})),
|
||||
addApproval: (approval) => set((s) => ({
|
||||
pendingApprovals: [...s.pendingApprovals, approval]
|
||||
})),
|
||||
}))
|
||||
)
|
||||
```
|
||||
|
||||
### 狀態分層策略
|
||||
|
||||
| 層級 | 工具 | 用途 |
|
||||
|------|------|------|
|
||||
| **全域 UI 狀態** | Zustand | Agent 狀態、Sidebar 開關、Theme |
|
||||
| **伺服器資料快取** | TanStack Query | API 回應快取、自動重新驗證 |
|
||||
| **表單狀態** | React Hook Form | 表單驗證、欄位狀態 |
|
||||
| **元件局部狀態** | useState | 簡單 UI 切換 |
|
||||
|
||||
### 禁止事項
|
||||
|
||||
```typescript
|
||||
// ❌ 禁止:Redux
|
||||
import { createStore } from 'redux'
|
||||
|
||||
// ❌ 禁止:Context API 做複雜狀態管理
|
||||
const GlobalContext = createContext<ComplexState>(...)
|
||||
|
||||
// ❌ 禁止:單一巨大 Store
|
||||
const useGodStore = create(() => ({
|
||||
agent: ...,
|
||||
plugins: ...,
|
||||
pipelines: ..., // 太多!
|
||||
}))
|
||||
|
||||
// ✅ 正確:Slice Pattern 分拆
|
||||
const useAgentStore = create(...)
|
||||
const usePluginStore = create(...)
|
||||
const usePipelineStore = create(...)
|
||||
```
|
||||
|
||||
## 理由
|
||||
|
||||
### 1. 效能優勢
|
||||
|
||||
| 特性 | Redux | Zustand |
|
||||
|------|-------|---------|
|
||||
| Bundle Size | ~7KB | ~1KB |
|
||||
| Boilerplate | 高 | 極低 |
|
||||
| Re-render 控制 | 需 memo/selector | 內建 selector |
|
||||
| SSE/WebSocket | 需 middleware | 原生支援 |
|
||||
|
||||
### 2. SSE 整合範例
|
||||
|
||||
```typescript
|
||||
// hooks/useAgentThinking.ts
|
||||
export function useAgentThinking() {
|
||||
const appendThinking = useAgentStore((s) => s.appendThinking)
|
||||
|
||||
useEffect(() => {
|
||||
const eventSource = new EventSource('/v1/agent/thinking')
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
appendThinking(event.data) // 直接更新 Zustand
|
||||
}
|
||||
|
||||
return () => eventSource.close()
|
||||
}, [appendThinking])
|
||||
}
|
||||
```
|
||||
|
||||
### 3. TanStack Query 協作
|
||||
|
||||
```typescript
|
||||
// hooks/useApprovals.ts
|
||||
export function useApprovals() {
|
||||
return useQuery({
|
||||
queryKey: ['approvals', 'pending'],
|
||||
queryFn: () => api.listApprovals({ status: 'pending' }),
|
||||
refetchInterval: 5000, // 每 5 秒輪詢
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 後果
|
||||
|
||||
### 優點
|
||||
|
||||
- **極度輕量** 不增加 bundle 負擔
|
||||
- **高頻更新** 完美處理 SSE/WebSocket 串流
|
||||
- **簡單 API** 降低學習曲線
|
||||
- **TypeScript 友善** 完整型別推導
|
||||
|
||||
### 缺點
|
||||
|
||||
- **生態較小** 相比 Redux 社群資源較少
|
||||
- **DevTools** 功能不如 Redux DevTools 強大
|
||||
|
||||
### 風險
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| Store 肥大化 | 強制執行 Slice Pattern,Code Review 把關 |
|
||||
| 狀態同步錯誤 | 搭配 TanStack Query 管理伺服器狀態 |
|
||||
|
||||
---
|
||||
|
||||
## Gate 0 實作細節
|
||||
|
||||
### 1. Dashboard SSE Store
|
||||
|
||||
```typescript
|
||||
// stores/dashboard.store.ts
|
||||
interface DashboardState {
|
||||
hosts: HostStatus[]
|
||||
connectionStatus: 'connecting' | 'connected' | 'disconnected' | 'error'
|
||||
lastUpdate: Date | null
|
||||
|
||||
// SSE 控制
|
||||
connect: (apiUrl: string) => void
|
||||
disconnect: () => void
|
||||
}
|
||||
|
||||
export const useDashboardStore = create<DashboardState>((set, get) => ({
|
||||
hosts: [],
|
||||
connectionStatus: 'disconnected',
|
||||
lastUpdate: null,
|
||||
|
||||
connect: (apiUrl) => {
|
||||
const eventSource = new EventSource(`${apiUrl}/api/v1/dashboard/stream`)
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data)
|
||||
set({ hosts: data.hosts, lastUpdate: new Date() })
|
||||
}
|
||||
|
||||
eventSource.onerror = () => set({ connectionStatus: 'error' })
|
||||
eventSource.onopen = () => set({ connectionStatus: 'connected' })
|
||||
},
|
||||
|
||||
disconnect: () => {
|
||||
// AbortController cleanup
|
||||
}
|
||||
}))
|
||||
```
|
||||
|
||||
### 2. Approval Multi-Sig 狀態機
|
||||
|
||||
```typescript
|
||||
// stores/approval.store.ts
|
||||
interface ApprovalState {
|
||||
pendingApprovals: Approval[]
|
||||
selectedApproval: Approval | null
|
||||
signingStatus: 'idle' | 'signing' | 'success' | 'error'
|
||||
|
||||
// Actions
|
||||
signApproval: (id: string, userId: string, role: string) => Promise<void>
|
||||
refreshApprovals: () => Promise<void>
|
||||
}
|
||||
|
||||
// 狀態機轉換圖
|
||||
// pending → (簽核) → pending (需更多簽章)
|
||||
// pending → (簽核) → approved (達到閾值)
|
||||
// pending → (拒絕) → rejected
|
||||
// pending → (TOCTOU) → voided (資源狀態改變)
|
||||
```
|
||||
|
||||
### 3. SSE + Zustand 整合模式
|
||||
|
||||
**企業級 SSE 最佳實踐:**
|
||||
|
||||
| 特性 | 實作 |
|
||||
|------|------|
|
||||
| **Buffer** | 累積 5 秒內的更新,批次 setState |
|
||||
| **AbortController** | 元件 unmount 時正確關閉連線 |
|
||||
| **Reconnection** | 指數退避重連 (1s → 2s → 4s → max 30s) |
|
||||
| **Heartbeat** | 每 30 秒 ping,超時則重連 |
|
||||
|
||||
```typescript
|
||||
// 企業級 SSE Hook 範例
|
||||
function useSSE(url: string) {
|
||||
const abortControllerRef = useRef<AbortController>()
|
||||
const bufferRef = useRef<HostStatus[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
abortControllerRef.current = new AbortController()
|
||||
|
||||
const flushBuffer = setInterval(() => {
|
||||
if (bufferRef.current.length > 0) {
|
||||
useDashboardStore.setState({ hosts: bufferRef.current })
|
||||
bufferRef.current = []
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
return () => {
|
||||
abortControllerRef.current?.abort()
|
||||
clearInterval(flushBuffer)
|
||||
}
|
||||
}, [url])
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 參考
|
||||
|
||||
- [Zustand](https://zustand-demo.pmnd.rs/)
|
||||
- [TanStack Query](https://tanstack.com/query)
|
||||
- ADR-002: Nothing.tech 設計系統 (動畫需求)
|
||||
- [approvals-contract.yaml](../api/approvals-contract.yaml) - API 契約定義
|
||||
178
docs/adr/ADR-005-bff-architecture.md
Normal file
178
docs/adr/ADR-005-bff-architecture.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# ADR-005: 導入 BFF (Backend-For-Frontend) API 閘道模式
|
||||
|
||||
> **狀態**: Accepted
|
||||
> **日期**: 2026-03-19
|
||||
> **決策者**: CTO + CIO
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 的底層是由 leWOOOgo Engine 驅動的微服務/Plugin 架構。如果讓 Next.js 前端直接呼叫:
|
||||
- 多個分散的 Plugin API
|
||||
- Ollama / Claude API
|
||||
- PostgreSQL / Redis
|
||||
- K8s API
|
||||
|
||||
會導致:
|
||||
1. 前端邏輯過於肥大
|
||||
2. 極高的資安外洩風險
|
||||
3. 難以實施統一的身分驗證與權限控制
|
||||
|
||||
## 決策
|
||||
|
||||
**強制實施 BFF (Backend-For-Frontend) 架構**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ AWOOOI 架構 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────────────┐ │
|
||||
│ │ Next.js │ ──────→ │ FastAPI BFF │ │
|
||||
│ │ 前端 │ HTTPS │ Gateway │ │
|
||||
│ └─────────┘ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────┼───────────────────┐ │
|
||||
│ ↓ ↓ ↓ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ leWOOOgo │ │ ClawBot │ │ PostgreSQL │ │
|
||||
│ │ Plugins │ │ (Ollama) │ │ Redis │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
│ ════════════════════════════════════════════════════════════ │
|
||||
│ DMZ (前端無法直達) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 核心規則
|
||||
|
||||
| 規則 | 說明 |
|
||||
|------|------|
|
||||
| **單一入口** | 前端只能打 `https://api.awoooi.wooo.work/v1/*` |
|
||||
| **禁止直連** | 前端禁止直連 PostgreSQL、Redis、K8s、Ollama |
|
||||
| **身分驗證** | 所有請求經 BFF JWT 驗證 |
|
||||
| **資料脫敏** | Privacy Shield 在 BFF 層攔截機敏資料 |
|
||||
|
||||
### BFF 層職責
|
||||
|
||||
```python
|
||||
# apps/api/src/routes/agent.py
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from src.auth import require_auth
|
||||
from src.privacy import PrivacyShield
|
||||
from src.services import clawbot_client, approval_service
|
||||
|
||||
router = APIRouter(prefix="/agent", tags=["Agent"])
|
||||
|
||||
@router.post("/chat")
|
||||
async def chat_with_agent(
|
||||
request: ChatRequest,
|
||||
user: User = Depends(require_auth), # 1. 身分驗證
|
||||
):
|
||||
# 2. 資料脫敏
|
||||
sanitized = PrivacyShield.sanitize(request.message)
|
||||
|
||||
# 3. 聚合多個後端服務
|
||||
response = await clawbot_client.chat(sanitized, user_id=user.id)
|
||||
|
||||
# 4. 判斷是否需要 Approval
|
||||
if response.requires_action:
|
||||
approval = await approval_service.create(
|
||||
action=response.suggested_action,
|
||||
user_id=user.id,
|
||||
)
|
||||
response.approval_id = approval.id
|
||||
|
||||
return response
|
||||
```
|
||||
|
||||
### 禁止事項
|
||||
|
||||
```typescript
|
||||
// ❌ 禁止:前端直連資料庫
|
||||
const client = new Client({ connectionString: 'postgresql://...' })
|
||||
|
||||
// ❌ 禁止:前端直接呼叫 Ollama
|
||||
const response = await fetch('http://192.168.0.188:11434/api/generate')
|
||||
|
||||
// ❌ 禁止:前端直接操作 K8s
|
||||
const k8s = new KubeConfig()
|
||||
|
||||
// ✅ 正確:透過 BFF API
|
||||
const response = await fetch('https://api.awoooi.wooo.work/v1/agent/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify({ message: '...' }),
|
||||
})
|
||||
```
|
||||
|
||||
## 理由
|
||||
|
||||
### 1. Zero Trust 網路隔離
|
||||
|
||||
| 元件 | 網路可達性 |
|
||||
|------|-----------|
|
||||
| Next.js (前端) | Public Internet |
|
||||
| FastAPI BFF | DMZ (僅接受前端) |
|
||||
| PostgreSQL | Internal Only |
|
||||
| Redis | Internal Only |
|
||||
| Ollama | Internal Only |
|
||||
| K8s API | Internal Only |
|
||||
|
||||
### 2. 統一關注點
|
||||
|
||||
| 關注點 | 處理位置 |
|
||||
|--------|---------|
|
||||
| 身分驗證 | BFF Middleware |
|
||||
| 權限檢查 | BFF Dependency |
|
||||
| 請求限流 | BFF / Nginx |
|
||||
| 資料脫敏 | BFF Privacy Shield |
|
||||
| 審計日誌 | BFF Logger |
|
||||
|
||||
### 3. 資料聚合
|
||||
|
||||
```python
|
||||
# 一個 API 呼叫 = 多個後端服務聚合
|
||||
@router.get("/dashboard")
|
||||
async def get_dashboard(user: User = Depends(require_auth)):
|
||||
# 平行取得多個資料源
|
||||
agent_status, pending_approvals, recent_alerts = await asyncio.gather(
|
||||
clawbot_client.get_status(),
|
||||
approval_service.list_pending(user.id),
|
||||
alert_service.list_recent(limit=10),
|
||||
)
|
||||
|
||||
return DashboardResponse(
|
||||
agent=agent_status,
|
||||
approvals=pending_approvals,
|
||||
alerts=recent_alerts,
|
||||
)
|
||||
```
|
||||
|
||||
## 後果
|
||||
|
||||
### 優點
|
||||
|
||||
- **Zero Trust** 真正的網路隔離
|
||||
- **前端精簡** 只負責渲染 UI
|
||||
- **統一治理** 所有安全策略集中管理
|
||||
- **可觀測性** 單一入口易於監控
|
||||
|
||||
### 缺點
|
||||
|
||||
- **開發成本** 新功能需在 BFF 層多寫一層
|
||||
- **延遲增加** 多一層網路跳躍 (~1-5ms)
|
||||
|
||||
### 風險
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| BFF 成為瓶頸 | 水平擴展 + Redis 快取 |
|
||||
| 開發速度下降 | OpenAPI 自動生成 Client SDK |
|
||||
|
||||
## 參考
|
||||
|
||||
- [BFF Pattern](https://samnewman.io/patterns/architectural/bff/)
|
||||
- api-contract.yaml (BFF 對外契約)
|
||||
- ADR-001: MCP Protocol (內部服務整合)
|
||||
297
docs/adr/ADR-006-ai-fallback-strategy.md
Normal file
297
docs/adr/ADR-006-ai-fallback-strategy.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# ADR-006: AI 降級備援策略
|
||||
|
||||
> **狀態**: 已接受
|
||||
> **日期**: 2026-03-20
|
||||
> **決策者**: CTO, CEO
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 系統高度依賴 AI 功能,包括 AI Copilot、異常偵測、智能摘要等。
|
||||
當本地 Ollama 服務不可用時,需要有完善的降級備援機制,同時嚴格控制雲端 API 成本。
|
||||
|
||||
### CEO 指示 #2
|
||||
|
||||
> 雲端備援的順序採 **Gemini API 然後才是 Claude API**,並且要有效控管、監控,
|
||||
> API Token 使用的數量,要搭配告警機制,避免費用暴增!
|
||||
|
||||
---
|
||||
|
||||
## 決策
|
||||
|
||||
### 1. AI 服務優先順序
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 優先級 1: Ollama (本地) │
|
||||
│ 192.168.0.188:11434 │
|
||||
│ 成本: $0 / 延遲: ~200ms │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
│ 失敗
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 優先級 2: Gemini API (雲端備援 - 優先) │
|
||||
│ 成本: ~$0.001/1K tokens │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
│ 失敗
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 優先級 3: Claude API (雲端備援 - 次選) │
|
||||
│ 成本: ~$0.008/1K tokens │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
│ 失敗
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 優先級 4: 靜態回應 (完全降級) │
|
||||
│ 返回預設訊息,不調用任何 AI │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. Circuit Breaker 機制
|
||||
|
||||
```python
|
||||
# apps/api/app/services/ai/circuit_breaker.py
|
||||
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import asyncio
|
||||
|
||||
class CircuitState(Enum):
|
||||
CLOSED = "closed" # 正常
|
||||
OPEN = "open" # 熔斷
|
||||
HALF_OPEN = "half_open" # 試探
|
||||
|
||||
class CircuitBreaker:
|
||||
def __init__(
|
||||
self,
|
||||
failure_threshold: int = 5, # 連續失敗 5 次觸發熔斷
|
||||
recovery_timeout: int = 60, # 熔斷後 60 秒嘗試恢復
|
||||
half_open_max_calls: int = 3 # 半開狀態最多 3 次試探
|
||||
):
|
||||
self.state = CircuitState.CLOSED
|
||||
self.failure_count = 0
|
||||
self.last_failure_time = None
|
||||
self.half_open_calls = 0
|
||||
# ...
|
||||
|
||||
async def call(self, func, *args, **kwargs):
|
||||
if self.state == CircuitState.OPEN:
|
||||
if self._should_try_recovery():
|
||||
self.state = CircuitState.HALF_OPEN
|
||||
self.half_open_calls = 0
|
||||
else:
|
||||
raise CircuitOpenError("Circuit is open")
|
||||
|
||||
try:
|
||||
result = await func(*args, **kwargs)
|
||||
self._on_success()
|
||||
return result
|
||||
except Exception as e:
|
||||
self._on_failure()
|
||||
raise
|
||||
```
|
||||
|
||||
### 3. Token 使用量監控與告警
|
||||
|
||||
#### 每日/每月配額
|
||||
|
||||
| API | 每日上限 | 每月上限 | 告警閾值 |
|
||||
|-----|---------|---------|---------|
|
||||
| Gemini | 100K tokens | 2M tokens | 70% |
|
||||
| Claude | 50K tokens | 500K tokens | 70% |
|
||||
|
||||
#### 監控 Schema
|
||||
|
||||
```python
|
||||
# apps/api/app/models/ai_usage.py
|
||||
|
||||
class AIUsageLog(Base):
|
||||
__tablename__ = "ai_usage_logs"
|
||||
|
||||
id = Column(UUID, primary_key=True)
|
||||
provider = Column(String) # ollama, gemini, claude
|
||||
model = Column(String)
|
||||
input_tokens = Column(Integer)
|
||||
output_tokens = Column(Integer)
|
||||
latency_ms = Column(Integer)
|
||||
success = Column(Boolean)
|
||||
error_message = Column(String, nullable=True)
|
||||
user_id = Column(UUID, ForeignKey("users.id"))
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
```
|
||||
|
||||
#### 告警規則
|
||||
|
||||
```yaml
|
||||
# k8s/monitoring/prometheus/ai-usage-alerts.yaml
|
||||
groups:
|
||||
- name: ai-usage-alerts
|
||||
rules:
|
||||
# Gemini 每日用量 70% 告警
|
||||
- alert: GeminiDailyUsageWarning
|
||||
expr: |
|
||||
sum(increase(ai_tokens_total{provider="gemini"}[24h])) > 70000
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Gemini API 每日用量已達 70%"
|
||||
description: "今日 Gemini 已使用 {{ $value | humanize }} tokens"
|
||||
|
||||
# Gemini 每日用量 90% 嚴重告警
|
||||
- alert: GeminiDailyUsageCritical
|
||||
expr: |
|
||||
sum(increase(ai_tokens_total{provider="gemini"}[24h])) > 90000
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Gemini API 每日用量已達 90%,即將觸發限流"
|
||||
|
||||
# Claude 每日用量 70% 告警
|
||||
- alert: ClaudeDailyUsageWarning
|
||||
expr: |
|
||||
sum(increase(ai_tokens_total{provider="claude"}[24h])) > 35000
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Claude API 每日用量已達 70%"
|
||||
|
||||
# Ollama 連續失敗告警
|
||||
- alert: OllamaConsecutiveFailures
|
||||
expr: |
|
||||
increase(ai_requests_failed_total{provider="ollama"}[5m]) > 5
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Ollama 服務可能已離線"
|
||||
description: "過去 5 分鐘 Ollama 請求失敗超過 5 次,已啟動雲端備援"
|
||||
|
||||
# 月度預算 50% 提醒
|
||||
- alert: MonthlyAIBudgetWarning
|
||||
expr: |
|
||||
(
|
||||
sum(increase(ai_tokens_total{provider="gemini"}[30d])) * 0.000001 +
|
||||
sum(increase(ai_tokens_total{provider="claude"}[30d])) * 0.000008
|
||||
) > 5
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "AI 月度成本已達 $5 (預算 50%)"
|
||||
```
|
||||
|
||||
### 4. 成本預估
|
||||
|
||||
| 場景 | Gemini | Claude | 月成本 |
|
||||
|------|--------|--------|--------|
|
||||
| **正常** (Ollama 100%) | 0 | 0 | $0 |
|
||||
| **輕度降級** (Ollama 90%, Gemini 10%) | ~200K | 0 | ~$0.20 |
|
||||
| **中度降級** (Gemini 80%, Claude 20%) | ~1.6M | ~400K | ~$5 |
|
||||
| **完全降級** (雲端 100%) | ~2M | ~500K | ~$10 |
|
||||
|
||||
### 5. 實作範例
|
||||
|
||||
```python
|
||||
# apps/api/app/services/ai/router.py
|
||||
|
||||
from app.services.ai.providers import OllamaProvider, GeminiProvider, ClaudeProvider
|
||||
from app.services.ai.circuit_breaker import CircuitBreaker
|
||||
from app.services.ai.usage_tracker import UsageTracker
|
||||
|
||||
class AIRouter:
|
||||
def __init__(self):
|
||||
self.ollama = OllamaProvider()
|
||||
self.gemini = GeminiProvider()
|
||||
self.claude = ClaudeProvider()
|
||||
|
||||
self.ollama_circuit = CircuitBreaker(failure_threshold=3, recovery_timeout=30)
|
||||
self.gemini_circuit = CircuitBreaker(failure_threshold=5, recovery_timeout=60)
|
||||
self.claude_circuit = CircuitBreaker(failure_threshold=5, recovery_timeout=60)
|
||||
|
||||
self.usage_tracker = UsageTracker()
|
||||
|
||||
async def generate(self, prompt: str, user_id: str) -> AIResponse:
|
||||
providers = [
|
||||
("ollama", self.ollama, self.ollama_circuit),
|
||||
("gemini", self.gemini, self.gemini_circuit),
|
||||
("claude", self.claude, self.claude_circuit),
|
||||
]
|
||||
|
||||
for name, provider, circuit in providers:
|
||||
# 檢查配額
|
||||
if name in ["gemini", "claude"]:
|
||||
if await self.usage_tracker.is_quota_exceeded(name):
|
||||
logger.warning(f"{name} daily quota exceeded, skipping")
|
||||
continue
|
||||
|
||||
try:
|
||||
result = await circuit.call(provider.generate, prompt)
|
||||
|
||||
# 記錄使用量
|
||||
await self.usage_tracker.log(
|
||||
provider=name,
|
||||
input_tokens=result.input_tokens,
|
||||
output_tokens=result.output_tokens,
|
||||
user_id=user_id,
|
||||
success=True
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except CircuitOpenError:
|
||||
logger.info(f"{name} circuit is open, trying next provider")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f"{name} failed: {e}, trying next provider")
|
||||
await self.usage_tracker.log(
|
||||
provider=name,
|
||||
error_message=str(e),
|
||||
user_id=user_id,
|
||||
success=False
|
||||
)
|
||||
continue
|
||||
|
||||
# 所有 AI 都失敗,返回靜態回應
|
||||
return AIResponse(
|
||||
content="抱歉,AI 服務暫時不可用。請稍後再試,或聯繫管理員。",
|
||||
provider="fallback",
|
||||
tokens=0
|
||||
)
|
||||
```
|
||||
|
||||
### 6. Dashboard 展示
|
||||
|
||||
AI 用量監控面板應顯示:
|
||||
|
||||
- 今日各 Provider 使用量 (tokens)
|
||||
- 本月累計成本 (USD)
|
||||
- 各 Provider 健康狀態 (綠/黃/紅)
|
||||
- 平均延遲 (ms)
|
||||
- 成功率 (%)
|
||||
|
||||
---
|
||||
|
||||
## 影響
|
||||
|
||||
### 正面
|
||||
|
||||
- 確保 AI 功能高可用性
|
||||
- 成本可控、可預測
|
||||
- 即時告警避免帳單爆炸
|
||||
|
||||
### 需要注意
|
||||
|
||||
- 需維護多個 API Key
|
||||
- 不同 Provider 回應品質可能有差異
|
||||
- 需要處理 API 格式轉換
|
||||
|
||||
---
|
||||
|
||||
## 變更記錄
|
||||
|
||||
| 日期 | 版本 | 變更 | 作者 |
|
||||
|------|------|------|------|
|
||||
| 2026-03-20 | v1.0 | 初版建立 | CTO |
|
||||
|
||||
---
|
||||
|
||||
*此 ADR 記錄 AI 降級備援策略的決策過程與實作規範。*
|
||||
234
docs/adr/ADR-007-data-retention-policy.md
Normal file
234
docs/adr/ADR-007-data-retention-policy.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# ADR-007: 資料保留策略
|
||||
|
||||
> **狀態**: 已接受
|
||||
> **日期**: 2026-03-20
|
||||
> **決策者**: CEO, CTO, CIO
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
需要定義各類型資料的保留時間 (TTL),確保:
|
||||
1. 系統效能不因資料累積而下降
|
||||
2. 重要資料有足夠的回溯時間
|
||||
3. 儲存成本可控
|
||||
|
||||
### CEO 指示 #7
|
||||
|
||||
> 熱資料 (Redis/即時查詢) TTL 7 天 => 初期是否也保留 6 個月?要確認數據量有多大?
|
||||
|
||||
---
|
||||
|
||||
## 決策
|
||||
|
||||
### 資料分層策略
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Layer 1: 熱資料 (Redis) │
|
||||
│ TTL: 7-30 天 │
|
||||
│ 用途: 即時查詢、快取、Session │
|
||||
│ 預估容量: ~500MB │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│ 過期後
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Layer 2: 溫資料 (PostgreSQL) │
|
||||
│ TTL: 6 個月 (CEO 指示) │
|
||||
│ 用途: 歷史查詢、報表、分析 │
|
||||
│ 預估容量: ~5GB/月 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│ 過期後
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Layer 3: 冷資料 (歸檔) │
|
||||
│ TTL: 永久 (審計日誌) / 1 年 (一般) │
|
||||
│ 用途: 合規、稽核、法律要求 │
|
||||
│ 預估容量: ~10GB/年 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 各資料類型 TTL 定義
|
||||
|
||||
#### Redis 熱資料 (Layer 1)
|
||||
|
||||
| 資料類型 | TTL | 說明 | 預估大小 |
|
||||
|---------|-----|------|---------|
|
||||
| Session Token | 7 天 | 用戶登入狀態 | ~1KB/session |
|
||||
| Dashboard 快取 | 5 分鐘 | 即時指標聚合 | ~10KB |
|
||||
| 主機狀態快取 | 30 秒 | 即時健康狀態 | ~2KB/host |
|
||||
| AI 回應快取 | 1 小時 | 相同問題快取 | ~5KB/entry |
|
||||
| 限流計數器 | 1 分鐘 | Rate Limiting | ~100B/user |
|
||||
|
||||
**Redis 容量評估**:
|
||||
- 4 台主機 × 2KB = 8KB (即時狀態)
|
||||
- 100 用戶 × 1KB = 100KB (Session)
|
||||
- Dashboard 快取 = 50KB
|
||||
- AI 快取 (1000 條) = 5MB
|
||||
- **總計: ~10MB (遠低於 Redis 16GB 容量)**
|
||||
|
||||
> ✅ **結論**: Redis 熱資料保持短 TTL (7-30 天) 是合理的,不需要延長至 6 個月。
|
||||
> Redis 用於快取和即時查詢,歷史資料應存放在 PostgreSQL。
|
||||
|
||||
#### PostgreSQL 溫資料 (Layer 2)
|
||||
|
||||
| 資料類型 | TTL | 說明 | 預估大小 |
|
||||
|---------|-----|------|---------|
|
||||
| 監控指標 | 6 個月 | CPU/Memory/Disk 歷史 | ~1GB/月 |
|
||||
| 告警記錄 | 6 個月 | 歷史告警 | ~100MB/月 |
|
||||
| 部署記錄 | 6 個月 | Pipeline 執行歷史 | ~50MB/月 |
|
||||
| 工單記錄 | 6 個月 | 處理歷史 | ~20MB/月 |
|
||||
| AI 對話記錄 | 6 個月 | Copilot 歷史 | ~500MB/月 |
|
||||
| 用戶操作記錄 | 6 個月 | 行為追蹤 | ~200MB/月 |
|
||||
|
||||
**PostgreSQL 容量評估**:
|
||||
- 每月增量: ~2GB
|
||||
- 6 個月累計: ~12GB
|
||||
- **總計 (含索引): ~20GB**
|
||||
|
||||
> ✅ **結論**: PostgreSQL 溫資料保留 6 個月,符合 CEO 指示,容量可控。
|
||||
|
||||
#### 冷資料歸檔 (Layer 3)
|
||||
|
||||
| 資料類型 | TTL | 說明 |
|
||||
|---------|-----|------|
|
||||
| 審計日誌 | 永久 | 合規要求,不可刪除 |
|
||||
| 財務記錄 | 7 年 | 法律要求 |
|
||||
| 安全事件 | 3 年 | 資安稽核 |
|
||||
| 系統設定變更 | 1 年 | 變更追蹤 |
|
||||
|
||||
---
|
||||
|
||||
### 資料清理機制
|
||||
|
||||
#### 自動清理 Job
|
||||
|
||||
```python
|
||||
# apps/api/app/jobs/data_cleanup.py
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from app.database import get_db
|
||||
from app.models import Metric, Alert, Deployment, AIConversation
|
||||
|
||||
async def cleanup_expired_data():
|
||||
"""每日凌晨 3:00 執行"""
|
||||
six_months_ago = datetime.utcnow() - timedelta(days=180)
|
||||
|
||||
async with get_db() as db:
|
||||
# 清理過期監控指標
|
||||
deleted_metrics = await db.execute(
|
||||
delete(Metric).where(Metric.created_at < six_months_ago)
|
||||
)
|
||||
logger.info(f"Deleted {deleted_metrics.rowcount} expired metrics")
|
||||
|
||||
# 清理過期告警 (保留 acknowledged 狀態)
|
||||
deleted_alerts = await db.execute(
|
||||
delete(Alert).where(
|
||||
Alert.created_at < six_months_ago,
|
||||
Alert.status != 'archived' # 保留歸檔的告警
|
||||
)
|
||||
)
|
||||
logger.info(f"Deleted {deleted_alerts.rowcount} expired alerts")
|
||||
|
||||
# ... 其他資料類型
|
||||
|
||||
await db.commit()
|
||||
|
||||
# 更新 Prometheus 指標
|
||||
cleanup_records_deleted.labels(type="metrics").inc(deleted_metrics.rowcount)
|
||||
cleanup_records_deleted.labels(type="alerts").inc(deleted_alerts.rowcount)
|
||||
```
|
||||
|
||||
#### K8s CronJob
|
||||
|
||||
```yaml
|
||||
# k8s/jobs/data-cleanup-cronjob.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: awoooi-data-cleanup
|
||||
namespace: awoooi-prod
|
||||
spec:
|
||||
schedule: "0 3 * * *" # 每日凌晨 3:00
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: cleanup
|
||||
image: awoooi-api:latest
|
||||
command: ["python", "-m", "app.jobs.data_cleanup"]
|
||||
restartPolicy: OnFailure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 資料量監控
|
||||
|
||||
#### Prometheus 指標
|
||||
|
||||
```yaml
|
||||
# k8s/monitoring/prometheus/data-alerts.yaml
|
||||
groups:
|
||||
- name: data-storage-alerts
|
||||
rules:
|
||||
# PostgreSQL 容量警告
|
||||
- alert: PostgreSQLHighUsage
|
||||
expr: |
|
||||
pg_database_size_bytes{datname="awoooi"} > 15 * 1024 * 1024 * 1024
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "PostgreSQL 容量已達 15GB"
|
||||
description: "目前使用 {{ $value | humanize1024 }},建議檢查資料清理 Job"
|
||||
|
||||
# Redis 容量警告
|
||||
- alert: RedisHighMemory
|
||||
expr: |
|
||||
redis_memory_used_bytes{db="10"} > 1 * 1024 * 1024 * 1024
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Redis DB 10 記憶體使用超過 1GB"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 儲存成本評估
|
||||
|
||||
| 層級 | 6 個月容量 | 儲存類型 | 成本 |
|
||||
|------|-----------|---------|------|
|
||||
| Redis (熱) | ~10MB | 內存 | 包含在伺服器 |
|
||||
| PostgreSQL (溫) | ~20GB | SSD | 包含在伺服器 |
|
||||
| 歸檔 (冷) | ~10GB/年 | HDD/S3 | ~$0.5/月 |
|
||||
|
||||
**結論**: 採用自建伺服器,儲存成本基本為 $0 (已攤提)。
|
||||
|
||||
---
|
||||
|
||||
## 影響
|
||||
|
||||
### 正面
|
||||
|
||||
- 資料保留策略明確,符合 CEO 6 個月要求
|
||||
- Redis 維持高效能 (短 TTL)
|
||||
- 歷史資料可追溯
|
||||
- 儲存成本可控
|
||||
|
||||
### 需要注意
|
||||
|
||||
- 清理 Job 需要監控,確保正常執行
|
||||
- 歸檔資料需要定期備份
|
||||
- 審計日誌不可刪除,需永久保留
|
||||
|
||||
---
|
||||
|
||||
## 變更記錄
|
||||
|
||||
| 日期 | 版本 | 變更 | 作者 |
|
||||
|------|------|------|------|
|
||||
| 2026-03-20 | v1.0 | 初版建立 | CTO |
|
||||
|
||||
---
|
||||
|
||||
*此 ADR 記錄資料保留策略的決策過程與實作規範。*
|
||||
583
docs/adr/ADR-009-openclaw-agent-teams.md
Normal file
583
docs/adr/ADR-009-openclaw-agent-teams.md
Normal file
@@ -0,0 +1,583 @@
|
||||
# ADR-009: OpenClaw Agent Teams 架構
|
||||
|
||||
**狀態**: 提議中 → 研究完成
|
||||
**日期**: 2026-03-23
|
||||
**決策者**: 統帥 + AI 架構師
|
||||
**Phase**: 9.1-9.2 (SDK 研究 + 架構設計)
|
||||
|
||||
## 背景
|
||||
|
||||
AWOOOI 的核心價值是 "AI Sees. AI Acts. You Approve."
|
||||
|
||||
目前 OpenClaw 是單一 AI 大腦,面對複雜告警時:
|
||||
- 單一視角可能遺漏問題
|
||||
- 無法並行分析多個面向
|
||||
- 決策品質依賴單一模型
|
||||
|
||||
Claude 推出了 **Claude Agent SDK** (原 Claude Code SDK,2026-03-20 發布 v0.1.50),支援多 Agent 協調。我們評估將此概念整合進 AWOOOI 產品。
|
||||
|
||||
### SDK 研究結論 (2026-03-23)
|
||||
|
||||
| 項目 | 研究結果 |
|
||||
|------|---------|
|
||||
| **SDK 名稱** | `claude-agent-sdk` (PyPI) |
|
||||
| **最新版本** | v0.1.50 (2026-03-20) |
|
||||
| **Python 版本** | ≥ 3.10 |
|
||||
| **核心 API** | `query()`, `ClaudeSDKClient` |
|
||||
| **Subagent 支援** | ✅ 原生支援 (`AgentDefinition`) |
|
||||
| **自訂 Tools** | ✅ `@tool` 裝飾器 + MCP 整合 |
|
||||
|
||||
## 決策
|
||||
|
||||
**採用 Claude Agent SDK 實作 OpenClaw Agent Teams,升級為多專家共識決策架構。**
|
||||
|
||||
### 為何選擇 Claude Agent SDK (而非自建)
|
||||
|
||||
| 考量 | 自建方案 | Claude Agent SDK |
|
||||
|------|---------|------------------|
|
||||
| 開發時間 | 2-3 週 | 2-3 天 |
|
||||
| Tool 執行 | 需自行實作 | 內建 (Read, Edit, Bash...) |
|
||||
| Subagent | 需自行設計 | 原生支援 |
|
||||
| Session 管理 | 需自行實作 | 內建 (resume, fork) |
|
||||
| MCP 整合 | 需橋接 | 原生支援 |
|
||||
| 維護成本 | 高 | 低 (跟隨 Anthropic 更新) |
|
||||
|
||||
### 架構設計
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ OpenClaw Coordinator │
|
||||
│ (Team Lead Agent) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ↓ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Security │ │ BlastRadius │ │ Action │ │
|
||||
│ │ Agent │ │ Agent │ │ Planner │ │
|
||||
│ │ (資安評估) │ │ (影響範圍) │ │ (行動方案) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ ↓ ↓ ↓ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Consensus Engine │ │
|
||||
│ │ (共識引擎 - 加權投票) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Final Proposal │ │
|
||||
│ │ (統一提案 → 人類審批) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Agent 職責
|
||||
|
||||
| Agent | 職責 | 輸出 |
|
||||
|-------|------|------|
|
||||
| **Coordinator** | 分配任務、彙整共識 | Final Proposal |
|
||||
| **SecurityAgent** | 評估安全風險、權限影響 | Risk Score (0-10) |
|
||||
| **BlastRadiusAgent** | 分析影響範圍、相依服務 | Affected Services List |
|
||||
| **ActionPlannerAgent** | 規劃修復步驟、回滾方案 | Action Steps + Rollback |
|
||||
|
||||
### 共識機制
|
||||
|
||||
```python
|
||||
class ConsensusEngine:
|
||||
weights = {
|
||||
"security": 0.4, # 資安權重最高
|
||||
"blast_radius": 0.3, # 影響範圍次之
|
||||
"action_plan": 0.3, # 行動方案
|
||||
}
|
||||
|
||||
def calculate_confidence(self, results: dict) -> float:
|
||||
"""加權計算整體信心分數"""
|
||||
score = 0
|
||||
for agent, weight in self.weights.items():
|
||||
score += results[agent].confidence * weight
|
||||
return score
|
||||
|
||||
def should_auto_approve(self, confidence: float) -> bool:
|
||||
"""信心分數 > 0.9 且無高風險 → 可自動執行"""
|
||||
return confidence > 0.9 and not self.has_high_risk()
|
||||
```
|
||||
|
||||
## 技術實作
|
||||
|
||||
### 依賴 (Phase 9.2 研究結果)
|
||||
|
||||
```toml
|
||||
# apps/api/pyproject.toml
|
||||
[project.dependencies]
|
||||
# Phase 9: OpenClaw Agent Teams
|
||||
claude-agent-sdk = ">=0.1.50" # Claude Agent SDK (原 Claude Code SDK)
|
||||
# Note: SDK 自動包含 Claude Code CLI,無需額外安裝
|
||||
```
|
||||
|
||||
#### 安裝指令
|
||||
|
||||
```bash
|
||||
# 使用 uv (推薦)
|
||||
uv add claude-agent-sdk
|
||||
|
||||
# 使用 pip
|
||||
pip install claude-agent-sdk
|
||||
|
||||
# 驗證安裝
|
||||
python -c "from claude_agent_sdk import query; print('OK')"
|
||||
```
|
||||
|
||||
#### 環境變數
|
||||
|
||||
```bash
|
||||
# 必須
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
# 可選 (雲端備援,參考 ADR-006)
|
||||
export CLAUDE_CODE_USE_BEDROCK=1 # AWS Bedrock
|
||||
export CLAUDE_CODE_USE_VERTEX=1 # Google Vertex AI
|
||||
```
|
||||
|
||||
### 核心類別 (使用 Claude Agent SDK)
|
||||
|
||||
```python
|
||||
# apps/api/src/services/openclaw_team.py
|
||||
|
||||
import asyncio
|
||||
from claude_agent_sdk import (
|
||||
query,
|
||||
ClaudeAgentOptions,
|
||||
AgentDefinition,
|
||||
ClaudeSDKClient,
|
||||
AssistantMessage,
|
||||
ResultMessage,
|
||||
)
|
||||
from dataclasses import dataclass
|
||||
from typing import AsyncIterator
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentResult:
|
||||
agent: str
|
||||
analysis: str
|
||||
confidence: float
|
||||
risk_score: float | None = None
|
||||
affected_services: list[str] | None = None
|
||||
action_steps: list[str] | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Proposal:
|
||||
incident_id: str
|
||||
summary: str
|
||||
agent_results: list[AgentResult]
|
||||
consensus_score: float
|
||||
recommended_action: str
|
||||
auto_approvable: bool
|
||||
|
||||
|
||||
class OpenClawTeam:
|
||||
"""
|
||||
使用 Claude Agent SDK 實作多專家協調分析
|
||||
符合 leWOOOgo BRAIN 積木介面
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# 定義專家 Subagents
|
||||
self.agents = {
|
||||
"security-expert": AgentDefinition(
|
||||
description="資安專家,評估安全風險與權限影響",
|
||||
prompt="""你是 AWOOOI 的資安專家。
|
||||
分析告警的安全風險,評估:
|
||||
1. 是否涉及敏感資料
|
||||
2. 是否可能被利用
|
||||
3. 權限邊界是否被突破
|
||||
輸出 JSON: {"risk_score": 0-10, "analysis": "...", "confidence": 0-1}""",
|
||||
tools=["Read", "Grep"], # 只讀權限
|
||||
),
|
||||
"blast-radius": AgentDefinition(
|
||||
description="影響範圍分析師,評估相依服務與影響範圍",
|
||||
prompt="""你是 AWOOOI 的影響範圍分析師。
|
||||
分析告警的影響範圍:
|
||||
1. 直接影響的服務
|
||||
2. 間接相依的服務
|
||||
3. 使用者影響人數估計
|
||||
輸出 JSON: {"affected_services": [...], "blast_radius": "low|medium|high", "confidence": 0-1}""",
|
||||
tools=["Read", "Glob", "Grep"],
|
||||
),
|
||||
"action-planner": AgentDefinition(
|
||||
description="行動規劃師,制定修復步驟與回滾方案",
|
||||
prompt="""你是 AWOOOI 的行動規劃師。
|
||||
根據告警制定修復計畫:
|
||||
1. 立即修復步驟 (kubectl 指令)
|
||||
2. 驗證步驟
|
||||
3. 回滾方案
|
||||
注意: 所有 kubectl 必須帶 -n awoooi-prod
|
||||
輸出 JSON: {"action_steps": [...], "rollback_steps": [...], "confidence": 0-1}""",
|
||||
tools=["Read", "Glob"],
|
||||
),
|
||||
}
|
||||
|
||||
self.options = ClaudeAgentOptions(
|
||||
allowed_tools=["Read", "Glob", "Grep", "Agent"], # Agent 用於調用 Subagent
|
||||
agents=self.agents,
|
||||
system_prompt="""你是 OpenClaw Coordinator,AWOOOI 的 AI 決策引擎。
|
||||
你的任務是協調多個專家 Agent 分析告警,彙整共識並產出最終提案。
|
||||
呼叫順序: security-expert → blast-radius → action-planner
|
||||
最終輸出統一提案供人類審批。""",
|
||||
)
|
||||
|
||||
async def analyze_incident(self, incident: dict) -> Proposal:
|
||||
"""
|
||||
並行呼叫多個 Subagent 分析告警
|
||||
"""
|
||||
prompt = f"""
|
||||
分析以下告警並產出修復提案:
|
||||
|
||||
```json
|
||||
{json.dumps(incident, ensure_ascii=False, indent=2)}
|
||||
```
|
||||
|
||||
請依序呼叫以下 Agent:
|
||||
1. security-expert - 評估安全風險
|
||||
2. blast-radius - 分析影響範圍
|
||||
3. action-planner - 規劃修復步驟
|
||||
|
||||
收集所有分析結果後,使用 ConsensusEngine 邏輯 (security 40%, blast_radius 30%, action 30%)
|
||||
計算整體信心分數,並產出最終提案。
|
||||
|
||||
輸出格式:
|
||||
```json
|
||||
{{
|
||||
"summary": "一句話摘要",
|
||||
"agent_results": [...],
|
||||
"consensus_score": 0-1,
|
||||
"recommended_action": "建議的 kubectl 指令",
|
||||
"auto_approvable": true/false (>0.9 且無高風險)
|
||||
}}
|
||||
```
|
||||
"""
|
||||
|
||||
result_json = None
|
||||
async for message in query(prompt=prompt, options=self.options):
|
||||
if isinstance(message, ResultMessage):
|
||||
# 解析最終結果
|
||||
result_json = self._extract_json(message.result)
|
||||
|
||||
if not result_json:
|
||||
raise ValueError("Agent Team 未能產出有效提案")
|
||||
|
||||
return Proposal(
|
||||
incident_id=incident.get("id", "unknown"),
|
||||
summary=result_json.get("summary", ""),
|
||||
agent_results=self._parse_agent_results(result_json.get("agent_results", [])),
|
||||
consensus_score=result_json.get("consensus_score", 0),
|
||||
recommended_action=result_json.get("recommended_action", ""),
|
||||
auto_approvable=result_json.get("auto_approvable", False),
|
||||
)
|
||||
|
||||
def _extract_json(self, text: str) -> dict:
|
||||
"""從回應中提取 JSON"""
|
||||
import json
|
||||
import re
|
||||
match = re.search(r'```json\s*(.*?)\s*```', text, re.DOTALL)
|
||||
if match:
|
||||
return json.loads(match.group(1))
|
||||
return json.loads(text)
|
||||
|
||||
def _parse_agent_results(self, results: list) -> list[AgentResult]:
|
||||
"""解析各 Agent 結果"""
|
||||
return [
|
||||
AgentResult(
|
||||
agent=r.get("agent", "unknown"),
|
||||
analysis=r.get("analysis", ""),
|
||||
confidence=r.get("confidence", 0),
|
||||
risk_score=r.get("risk_score"),
|
||||
affected_services=r.get("affected_services"),
|
||||
action_steps=r.get("action_steps"),
|
||||
)
|
||||
for r in results
|
||||
]
|
||||
```
|
||||
|
||||
### 替代方案: ClaudeSDKClient (互動式)
|
||||
|
||||
```python
|
||||
# 適用於需要人機互動的場景
|
||||
async def interactive_analysis(incident: dict):
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
# 第一輪: 安全分析
|
||||
await client.query(f"使用 security-expert 分析: {json.dumps(incident)}")
|
||||
security_result = await collect_response(client)
|
||||
|
||||
# 人類可在此介入調整
|
||||
|
||||
# 第二輪: 影響範圍
|
||||
await client.query("繼續使用 blast-radius 分析影響範圍")
|
||||
blast_result = await collect_response(client)
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### API 端點
|
||||
|
||||
```python
|
||||
# apps/api/src/routes/incidents.py
|
||||
|
||||
@router.post("/api/v1/incidents/{incident_id}/analyze")
|
||||
async def analyze_with_team(incident_id: str):
|
||||
"""使用 Agent Team 分析告警"""
|
||||
incident = await get_incident(incident_id)
|
||||
team = OpenClawTeam()
|
||||
proposal = await team.analyze_incident(incident)
|
||||
|
||||
return {
|
||||
"proposal": proposal,
|
||||
"agent_results": proposal.agent_results,
|
||||
"consensus_score": proposal.consensus_score,
|
||||
"auto_approvable": proposal.auto_approvable
|
||||
}
|
||||
```
|
||||
|
||||
### UI 呈現
|
||||
|
||||
```tsx
|
||||
// apps/web/src/components/incident/agent-team-analysis.tsx
|
||||
|
||||
export function AgentTeamAnalysis({ proposal }: Props) {
|
||||
return (
|
||||
<GlassCard>
|
||||
<h3>{t('incident.teamAnalysis')}</h3>
|
||||
|
||||
{/* 各 Agent 分析結果 */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{proposal.agentResults.map(result => (
|
||||
<AgentResultCard
|
||||
key={result.agent}
|
||||
agent={result.agent}
|
||||
confidence={result.confidence}
|
||||
summary={result.summary}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 共識分數 */}
|
||||
<ConsensusScore score={proposal.consensusScore} />
|
||||
|
||||
{/* 最終提案 */}
|
||||
<ProposalCard proposal={proposal} />
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 對應 leWOOOgo 積木
|
||||
|
||||
| 積木類別 | 新增模組 |
|
||||
|---------|---------|
|
||||
| **BRAIN** | `SecurityAgent` |
|
||||
| **BRAIN** | `BlastRadiusAgent` |
|
||||
| **BRAIN** | `ActionPlannerAgent` |
|
||||
| **BRAIN** | `CoordinatorAgent` |
|
||||
| **BRAIN** | `ConsensusEngine` |
|
||||
|
||||
## 後果
|
||||
|
||||
### 優點
|
||||
|
||||
1. **多視角分析** - 不同專家 Agent 各司其職
|
||||
2. **共識決策** - 加權投票提高決策品質
|
||||
3. **可解釋性** - 每個 Agent 的分析過程透明
|
||||
4. **彈性擴展** - 可新增更多專家 Agent
|
||||
5. **差異化** - 競品無此功能
|
||||
|
||||
### 缺點
|
||||
|
||||
1. **成本增加** - 多 Agent 呼叫增加 API 費用
|
||||
2. **延遲增加** - 並行分析仍需等待最慢的 Agent
|
||||
3. **複雜度** - 共識機制需要調優
|
||||
|
||||
### 風險
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| API 成本爆炸 | 設定 Token 上限、快取策略 |
|
||||
| Agent 意見衝突 | 共識引擎加權投票 |
|
||||
| SDK 不穩定 | 先用 Anthropic SDK 模擬 |
|
||||
|
||||
## 與 leWOOOgo 整合 (ADR-003)
|
||||
|
||||
OpenClaw Agent Teams 作為 **BRAIN 積木** 整合進 leWOOOgo 架構:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ leWOOOgo Engine │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 🧱 INPUT ──────→ 🧠 BRAIN ──────────────→ 📢 OUTPUT │
|
||||
│ (Prometheus) │ (Telegram) │
|
||||
│ │ │
|
||||
│ ┌──────┴──────┐ │
|
||||
│ │ OpenClawTeam │ ← NEW: Agent Teams │
|
||||
│ │ (SDK-based) │ │
|
||||
│ └──────┬──────┘ │
|
||||
│ │ │
|
||||
│ ┌──────┴──────┐ │
|
||||
│ │ 🔧 ACTION │ │
|
||||
│ │ K8sExecutor │ │
|
||||
│ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### BRAIN 積木介面實作
|
||||
|
||||
```python
|
||||
# packages/lewooogo-brain/src/openclaw_team_plugin.py
|
||||
|
||||
from lewooogo_core.interfaces import AgentProvider, AgentInput, AgentOutput
|
||||
|
||||
|
||||
class OpenClawTeamPlugin(AgentProvider):
|
||||
"""
|
||||
leWOOOgo BRAIN 積木: OpenClaw Agent Teams
|
||||
符合 ADR-003 定義的 AgentProvider 介面
|
||||
"""
|
||||
|
||||
id = "openclaw-agent-team"
|
||||
name = "OpenClaw Agent Team"
|
||||
version = "0.1.0"
|
||||
category = "BRAIN"
|
||||
|
||||
def __init__(self):
|
||||
self.team = OpenClawTeam()
|
||||
|
||||
async def initialize(self) -> None:
|
||||
# 驗證 API Key
|
||||
assert os.environ.get("ANTHROPIC_API_KEY"), "Missing ANTHROPIC_API_KEY"
|
||||
|
||||
async def process(self, input: AgentInput) -> AgentOutput:
|
||||
proposal = await self.team.analyze_incident(input.payload)
|
||||
return AgentOutput(
|
||||
result=proposal,
|
||||
confidence=proposal.consensus_score,
|
||||
metadata={"agent_count": 3, "sdk_version": "0.1.50"},
|
||||
)
|
||||
|
||||
def get_capabilities(self) -> list[str]:
|
||||
return [
|
||||
"security-analysis",
|
||||
"blast-radius-analysis",
|
||||
"action-planning",
|
||||
"consensus-decision",
|
||||
]
|
||||
|
||||
async def health_check(self) -> dict:
|
||||
return {"status": "healthy", "sdk": "claude-agent-sdk"}
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
pass
|
||||
```
|
||||
|
||||
## 與 ADR-006 整合 (AI 備援)
|
||||
|
||||
Agent Teams 整合現有 AI Fallback 策略:
|
||||
|
||||
```
|
||||
優先級 1: Ollama (本地) → 簡單告警走 Ollama
|
||||
優先級 2: Claude Agent SDK → 複雜告警走 Agent Teams
|
||||
優先級 3: Gemini API → SDK 失敗時備援
|
||||
優先級 4: 靜態回應
|
||||
```
|
||||
|
||||
### 路由邏輯
|
||||
|
||||
```python
|
||||
class OpenClawRouter:
|
||||
async def route(self, incident: dict) -> Proposal:
|
||||
# 根據告警複雜度選擇處理器
|
||||
if self._is_simple_alert(incident):
|
||||
# 簡單告警: Ollama 足夠
|
||||
return await self.ollama_handler.analyze(incident)
|
||||
else:
|
||||
# 複雜告警: 使用 Agent Teams
|
||||
try:
|
||||
return await self.agent_team.analyze_incident(incident)
|
||||
except ClaudeSDKError:
|
||||
# SDK 失敗,降級到 Gemini
|
||||
return await self.gemini_fallback.analyze(incident)
|
||||
|
||||
def _is_simple_alert(self, incident: dict) -> bool:
|
||||
# 判斷邏輯: P3/P4 且影響單一服務 → 簡單
|
||||
severity = incident.get("severity", "P3")
|
||||
affected = incident.get("affected_services", [])
|
||||
return severity in ["P3", "P4"] and len(affected) <= 1
|
||||
```
|
||||
|
||||
## 實作計劃 (更新版)
|
||||
|
||||
| Phase | 內容 | 狀態 | 預估 |
|
||||
|-------|------|------|------|
|
||||
| 9.1 | ADR 審核 + SDK 研究 | ✅ 完成 | 0.5 天 |
|
||||
| 9.2 | SDK 整合 + POC | 🔜 下一步 | 1 天 |
|
||||
| 9.3 | 3 專家 Agent 實作 | | 2 天 |
|
||||
| 9.4 | ConsensusEngine + leWOOOgo 整合 | | 1.5 天 |
|
||||
| 9.5 | API 端點 + UI 呈現 | | 1.5 天 |
|
||||
| 9.6 | 測試 + 文檔 + ADR-006 整合 | | 1 天 |
|
||||
|
||||
**總計: 7.5 天** (原估 10 天,因 SDK 簡化減少)
|
||||
|
||||
### Phase 9.2 POC 驗證項目
|
||||
|
||||
```bash
|
||||
# 1. 安裝 SDK
|
||||
cd apps/api && uv add claude-agent-sdk
|
||||
|
||||
# 2. 建立測試腳本
|
||||
cat > scripts/test-agent-team.py << 'EOF'
|
||||
import asyncio
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
|
||||
|
||||
async def main():
|
||||
# 簡單 Subagent 測試
|
||||
options = ClaudeAgentOptions(
|
||||
allowed_tools=["Agent"],
|
||||
agents={
|
||||
"test-agent": AgentDefinition(
|
||||
description="測試 Agent",
|
||||
prompt="回答問題並回傳 JSON",
|
||||
tools=[],
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
async for msg in query(
|
||||
prompt="使用 test-agent 回答: 2+2=?",
|
||||
options=options,
|
||||
):
|
||||
print(msg)
|
||||
|
||||
asyncio.run(main())
|
||||
EOF
|
||||
|
||||
# 3. 執行測試
|
||||
python scripts/test-agent-team.py
|
||||
```
|
||||
|
||||
## 相關 ADR
|
||||
|
||||
- ADR-003: leWOOOgo 模組架構 (BRAIN 積木)
|
||||
- ADR-006: AI 備援策略 (Fallback 整合)
|
||||
- ADR-001: MCP Protocol 採用 (SDK 支援 MCP)
|
||||
|
||||
## 參考資料
|
||||
|
||||
- [Claude Agent SDK Overview](https://platform.claude.com/docs/en/agent-sdk/overview)
|
||||
- [Claude Agent SDK Quickstart](https://platform.claude.com/docs/en/agent-sdk/quickstart)
|
||||
- [Claude Agent SDK Python GitHub](https://github.com/anthropics/claude-agent-sdk-python)
|
||||
- [Claude Agent SDK Demos](https://github.com/anthropics/claude-agent-sdk-demos)
|
||||
- [LangGraph + Claude Agent SDK 整合](https://www.mager.co/blog/2026-03-07-langgraph-claude-agent-sdk-ultimate-guide/)
|
||||
|
||||
## 變更記錄
|
||||
|
||||
| 日期 | 版本 | 變更 | 作者 |
|
||||
|------|------|------|------|
|
||||
| 2026-03-23 | v0.1 | 初稿提議 | AI 架構師 |
|
||||
| 2026-03-23 | v0.2 | SDK 研究完成,加入具體整合方案 | AI 架構師 |
|
||||
Reference in New Issue
Block a user