forked from xiaohei/taiji-AI-PAD
更新api文档
This commit is contained in:
@@ -0,0 +1,740 @@
|
||||
# AKS Agent 执行方案设计
|
||||
|
||||
## 1. 问题分析
|
||||
|
||||
### 当前状态
|
||||
|
||||
```
|
||||
用户请求 → MCP Server → 本地 MCP 协议处理 → 工具执行
|
||||
↓
|
||||
(未使用 AKS Pod 的 access_url)
|
||||
```
|
||||
|
||||
**问题**:虽然 AKS 部署后返回了 `access_url` 和 `endpoints`,但当前代码并没有使用这些 URL 来调用 AKS 中运行的 Pod。
|
||||
|
||||
### 目标状态
|
||||
|
||||
```
|
||||
用户请求 → MCP Server → 判断 Agent 类型 → 转发到 AKS Pod → 返回结果
|
||||
↓
|
||||
本地执行(无 K8s 部署)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体架构图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Client[客户端]
|
||||
User[用户]
|
||||
end
|
||||
|
||||
subgraph MCPServer[MCP Server]
|
||||
API[API Gateway]
|
||||
Router[Agent Router]
|
||||
LocalHandler[本地 MCP Handler]
|
||||
K8sProxy[K8s Agent Proxy]
|
||||
end
|
||||
|
||||
subgraph AKS[Azure Kubernetes Service]
|
||||
Pod1[Agent Pod 1]
|
||||
Pod2[Agent Pod 2]
|
||||
Pod3[Agent Pod N]
|
||||
end
|
||||
|
||||
subgraph AgentManager[Agent Manager Service]
|
||||
AM[Agent Manager API]
|
||||
end
|
||||
|
||||
User --> API
|
||||
API --> Router
|
||||
Router -->|无 K8s 部署| LocalHandler
|
||||
Router -->|有 K8s 部署| K8sProxy
|
||||
K8sProxy --> Pod1
|
||||
K8sProxy --> Pod2
|
||||
K8sProxy --> Pod3
|
||||
|
||||
AM -.->|管理| Pod1
|
||||
AM -.->|管理| Pod2
|
||||
AM -.->|管理| Pod3
|
||||
```
|
||||
|
||||
### 2.2 执行流程图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant API as MCP Server API
|
||||
participant Router as Agent Router
|
||||
participant DB as 数据库
|
||||
participant Proxy as K8s Proxy
|
||||
participant Pod as AKS Agent Pod
|
||||
participant Local as 本地 Handler
|
||||
|
||||
User->>API: POST /agents/{id}/execute
|
||||
API->>DB: 获取 Agent 信息
|
||||
DB-->>API: Agent 数据
|
||||
|
||||
API->>Router: 路由决策
|
||||
|
||||
alt Agent 有 access_url
|
||||
Router->>Proxy: 转发请求
|
||||
Proxy->>Pod: HTTP POST /execute
|
||||
Pod-->>Proxy: 执行结果
|
||||
Proxy-->>API: 返回结果
|
||||
else Agent 无 K8s 部署
|
||||
Router->>Local: 本地执行
|
||||
Local-->>API: 执行结果
|
||||
end
|
||||
|
||||
API->>DB: 记录执行和计费
|
||||
API-->>User: 返回结果
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 详细设计
|
||||
|
||||
### 3.1 新增组件:K8s Agent Proxy
|
||||
|
||||
**文件位置**: `services/mcp-server/app/k8s_agent_proxy.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
K8s Agent Proxy - 负责转发请求到 AKS 部署的 Agent Pod
|
||||
"""
|
||||
|
||||
import httpx
|
||||
import structlog
|
||||
from typing import Dict, Any, Optional
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProxyConfig:
|
||||
"""代理配置"""
|
||||
timeout: float = 30.0
|
||||
max_retries: int = 3
|
||||
retry_delay: float = 1.0
|
||||
health_check_interval: int = 30
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProxyResult:
|
||||
"""代理执行结果"""
|
||||
success: bool
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
execution_time_ms: float = 0.0
|
||||
pod_name: Optional[str] = None
|
||||
status_code: Optional[int] = None
|
||||
|
||||
|
||||
class K8sAgentProxy:
|
||||
"""K8s Agent 代理类"""
|
||||
|
||||
def __init__(self, config: Optional[ProxyConfig] = None):
|
||||
self.config = config or ProxyConfig()
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
async def _get_client(self) -> httpx.AsyncClient:
|
||||
"""获取或创建 HTTP 客户端"""
|
||||
if self._client is None or self._client.is_closed:
|
||||
self._client = httpx.AsyncClient(
|
||||
timeout=self.config.timeout,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
access_url: str,
|
||||
request_data: Dict[str, Any],
|
||||
pod_name: Optional[str] = None,
|
||||
headers: Optional[Dict[str, str]] = None
|
||||
) -> ProxyResult:
|
||||
"""
|
||||
转发执行请求到 AKS Agent Pod
|
||||
|
||||
Args:
|
||||
access_url: Agent Pod 的访问 URL
|
||||
request_data: MCP 请求数据
|
||||
pod_name: Pod 名称(用于日志)
|
||||
headers: 额外的请求头
|
||||
|
||||
Returns:
|
||||
ProxyResult: 执行结果
|
||||
"""
|
||||
start_time = datetime.utcnow()
|
||||
|
||||
try:
|
||||
client = await self._get_client()
|
||||
|
||||
# 构建完整的执行 URL
|
||||
execute_url = f"{access_url.rstrip('/')}/execute"
|
||||
|
||||
logger.info(
|
||||
"转发请求到 AKS Agent",
|
||||
url=execute_url,
|
||||
pod_name=pod_name,
|
||||
method=request_data.get("method")
|
||||
)
|
||||
|
||||
# 合并请求头
|
||||
request_headers = {"Content-Type": "application/json"}
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
# 发送请求(带重试)
|
||||
response = await self._request_with_retry(
|
||||
client, execute_url, request_data, request_headers
|
||||
)
|
||||
|
||||
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
|
||||
|
||||
if response.status_code == 200:
|
||||
result_data = response.json()
|
||||
return ProxyResult(
|
||||
success=True,
|
||||
result=result_data,
|
||||
execution_time_ms=execution_time,
|
||||
pod_name=pod_name,
|
||||
status_code=response.status_code
|
||||
)
|
||||
else:
|
||||
error_detail = response.text
|
||||
try:
|
||||
error_detail = response.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ProxyResult(
|
||||
success=False,
|
||||
error=f"Pod 返回错误: {response.status_code} - {error_detail}",
|
||||
execution_time_ms=execution_time,
|
||||
pod_name=pod_name,
|
||||
status_code=response.status_code
|
||||
)
|
||||
|
||||
except httpx.TimeoutException as e:
|
||||
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
|
||||
logger.error("请求 AKS Agent 超时", pod_name=pod_name, error=str(e))
|
||||
return ProxyResult(
|
||||
success=False,
|
||||
error=f"请求超时: {str(e)}",
|
||||
execution_time_ms=execution_time,
|
||||
pod_name=pod_name
|
||||
)
|
||||
|
||||
except httpx.ConnectError as e:
|
||||
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
|
||||
logger.error("无法连接到 AKS Agent", pod_name=pod_name, error=str(e))
|
||||
return ProxyResult(
|
||||
success=False,
|
||||
error=f"连接失败: {str(e)}",
|
||||
execution_time_ms=execution_time,
|
||||
pod_name=pod_name
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
|
||||
logger.error("转发请求失败", pod_name=pod_name, error=str(e))
|
||||
return ProxyResult(
|
||||
success=False,
|
||||
error=f"执行失败: {str(e)}",
|
||||
execution_time_ms=execution_time,
|
||||
pod_name=pod_name
|
||||
)
|
||||
|
||||
async def _request_with_retry(
|
||||
self,
|
||||
client: httpx.AsyncClient,
|
||||
url: str,
|
||||
data: Dict[str, Any],
|
||||
headers: Dict[str, str]
|
||||
) -> httpx.Response:
|
||||
"""带重试的请求"""
|
||||
import asyncio
|
||||
|
||||
last_exception = None
|
||||
for attempt in range(self.config.max_retries):
|
||||
try:
|
||||
response = await client.post(url, json=data, headers=headers)
|
||||
return response
|
||||
except (httpx.TimeoutException, httpx.ConnectError) as e:
|
||||
last_exception = e
|
||||
if attempt < self.config.max_retries - 1:
|
||||
await asyncio.sleep(self.config.retry_delay * (attempt + 1))
|
||||
logger.warning(
|
||||
f"重试请求 {attempt + 1}/{self.config.max_retries}",
|
||||
url=url,
|
||||
error=str(e)
|
||||
)
|
||||
raise last_exception
|
||||
|
||||
async def health_check(self, access_url: str) -> bool:
|
||||
"""检查 Agent Pod 健康状态"""
|
||||
try:
|
||||
client = await self._get_client()
|
||||
health_url = f"{access_url.rstrip('/')}/health"
|
||||
response = await client.get(health_url, timeout=5.0)
|
||||
return response.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def close(self):
|
||||
"""关闭客户端"""
|
||||
if self._client and not self._client.is_closed:
|
||||
await self._client.aclose()
|
||||
self._client = None
|
||||
|
||||
|
||||
# 全局代理实例
|
||||
_k8s_proxy: Optional[K8sAgentProxy] = None
|
||||
|
||||
|
||||
def get_k8s_agent_proxy() -> K8sAgentProxy:
|
||||
"""获取全局 K8s Agent 代理实例"""
|
||||
global _k8s_proxy
|
||||
if _k8s_proxy is None:
|
||||
_k8s_proxy = K8sAgentProxy()
|
||||
return _k8s_proxy
|
||||
|
||||
|
||||
async def close_k8s_agent_proxy():
|
||||
"""关闭全局代理"""
|
||||
global _k8s_proxy
|
||||
if _k8s_proxy:
|
||||
await _k8s_proxy.close()
|
||||
_k8s_proxy = None
|
||||
```
|
||||
|
||||
### 3.2 修改 Agent 执行端点
|
||||
|
||||
**文件位置**: `services/mcp-server/app/routes/agents.py`
|
||||
|
||||
修改 `execute_agent` 函数:
|
||||
|
||||
```python
|
||||
@router.post("/{agent_id}/execute", response_model=ExecutionResult)
|
||||
async def execute_agent(
|
||||
agent_id: str,
|
||||
request: MCPRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
session_id: Optional[str] = None,
|
||||
) -> ExecutionResult:
|
||||
"""
|
||||
执行 Agent 请求。
|
||||
|
||||
如果 Agent 部署在 AKS 中,请求将被转发到对应的 Pod。
|
||||
否则,请求将在本地执行。
|
||||
"""
|
||||
state = get_state()
|
||||
|
||||
try:
|
||||
agent_uuid = uuid.UUID(agent_id)
|
||||
user_id = uuid.UUID(current_user["user_id"])
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID or user ID") from exc
|
||||
|
||||
agent = await db.get(Agent, agent_uuid)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
# 权限检查
|
||||
if agent.owner_id != user_id and current_user.get("role") != "super_admin":
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
|
||||
# 资源管控检查
|
||||
await enforce_resource_control(
|
||||
user_id=str(user_id),
|
||||
resource_type="agent",
|
||||
resource_id=agent_id,
|
||||
estimated_cost=Decimal("0.01"),
|
||||
db=db
|
||||
)
|
||||
|
||||
start_time = time.time()
|
||||
execution_id = str(uuid.uuid4())
|
||||
|
||||
try:
|
||||
# ========== 路由决策:K8s Pod 或本地执行 ==========
|
||||
if agent.access_url and agent.k8s_status == "Running":
|
||||
# 转发到 AKS Agent Pod
|
||||
result = await _execute_on_k8s_pod(
|
||||
agent=agent,
|
||||
request=request,
|
||||
execution_id=execution_id,
|
||||
user_id=str(user_id)
|
||||
)
|
||||
else:
|
||||
# 本地执行
|
||||
result = await _execute_locally(
|
||||
agent=agent,
|
||||
request=request,
|
||||
execution_id=execution_id,
|
||||
user_id=str(user_id),
|
||||
db=db
|
||||
)
|
||||
# ================================================
|
||||
|
||||
duration = time.time() - start_time
|
||||
|
||||
# 记录执行和计费
|
||||
await _record_execution_and_billing(
|
||||
db=db,
|
||||
agent=agent,
|
||||
request=request,
|
||||
result=result,
|
||||
execution_id=execution_id,
|
||||
start_time=start_time,
|
||||
duration=duration,
|
||||
session_id=session_id
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as exc:
|
||||
duration = time.time() - start_time
|
||||
logger.error("执行Agent任务失败", error=str(exc))
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
async def _execute_on_k8s_pod(
|
||||
agent: Agent,
|
||||
request: MCPRequest,
|
||||
execution_id: str,
|
||||
user_id: str
|
||||
) -> ExecutionResult:
|
||||
"""转发请求到 AKS Agent Pod"""
|
||||
from ..k8s_agent_proxy import get_k8s_agent_proxy
|
||||
|
||||
proxy = get_k8s_agent_proxy()
|
||||
|
||||
# 构建请求数据
|
||||
request_data = {
|
||||
"jsonrpc": request.jsonrpc,
|
||||
"id": str(request.id),
|
||||
"method": request.method,
|
||||
"params": request.params or {},
|
||||
"metadata": {
|
||||
"execution_id": execution_id,
|
||||
"user_id": user_id,
|
||||
"agent_id": str(agent.id)
|
||||
}
|
||||
}
|
||||
|
||||
# 转发请求
|
||||
proxy_result = await proxy.execute(
|
||||
access_url=agent.access_url,
|
||||
request_data=request_data,
|
||||
pod_name=agent.pod_name
|
||||
)
|
||||
|
||||
if proxy_result.success:
|
||||
return ExecutionResult(
|
||||
execution_id=execution_id,
|
||||
success=True,
|
||||
result=proxy_result.result,
|
||||
execution_time=proxy_result.execution_time_ms,
|
||||
started_at=datetime.utcnow(),
|
||||
completed_at=datetime.utcnow()
|
||||
)
|
||||
else:
|
||||
return ExecutionResult(
|
||||
execution_id=execution_id,
|
||||
success=False,
|
||||
error=proxy_result.error,
|
||||
execution_time=proxy_result.execution_time_ms,
|
||||
started_at=datetime.utcnow(),
|
||||
completed_at=datetime.utcnow()
|
||||
)
|
||||
|
||||
|
||||
async def _execute_locally(
|
||||
agent: Agent,
|
||||
request: MCPRequest,
|
||||
execution_id: str,
|
||||
user_id: str,
|
||||
db: AsyncSession
|
||||
) -> ExecutionResult:
|
||||
"""本地执行 MCP 请求"""
|
||||
state = get_state()
|
||||
handler = state.mcp_handler
|
||||
|
||||
if not handler:
|
||||
raise HTTPException(status_code=500, detail="MCP handler not initialized")
|
||||
|
||||
return await handler.execute_request(
|
||||
str(agent.id),
|
||||
request,
|
||||
user_id=user_id,
|
||||
db_session=db
|
||||
)
|
||||
```
|
||||
|
||||
### 3.3 AKS Agent Pod 端点规范
|
||||
|
||||
每个部署在 AKS 中的 Agent Pod 需要实现以下端点:
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|-----|------|------|
|
||||
| `/health` | GET | 健康检查 |
|
||||
| `/execute` | POST | 执行 MCP 请求 |
|
||||
| `/status` | GET | 获取 Agent 状态 |
|
||||
| `/metrics` | GET | 获取资源使用指标 |
|
||||
|
||||
#### 3.3.1 `/execute` 端点请求格式
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "request-uuid",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "tool_name",
|
||||
"arguments": {}
|
||||
},
|
||||
"metadata": {
|
||||
"execution_id": "exec-uuid",
|
||||
"user_id": "user-uuid",
|
||||
"agent_id": "agent-uuid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.2 `/execute` 端点响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "执行结果"
|
||||
}
|
||||
]
|
||||
},
|
||||
"execution_time_ms": 150.5,
|
||||
"resource_usage": {
|
||||
"cpu_ms": 50,
|
||||
"memory_mb": 128
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据库变更
|
||||
|
||||
### 4.1 Agent 表新增字段(已存在)
|
||||
|
||||
当前 `Agent` 模型已包含必要字段:
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|-----|------|------|
|
||||
| `access_url` | String(500) | Pod 访问 URL |
|
||||
| `pod_name` | String(100) | Pod 名称 |
|
||||
| `pod_ip` | String(45) | Pod IP |
|
||||
| `k8s_status` | String(20) | Pod 状态 |
|
||||
| `service_port` | Integer | Service 端口 |
|
||||
| `endpoints` | JSON | 端点字典 |
|
||||
|
||||
### 4.2 新增执行记录字段
|
||||
|
||||
在 `Execution` 表中添加:
|
||||
|
||||
```python
|
||||
# 执行位置
|
||||
execution_location = Column(String(20), default="local") # local, k8s
|
||||
pod_name = Column(String(100)) # 执行的 Pod 名称
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置变更
|
||||
|
||||
### 5.1 环境变量
|
||||
|
||||
```bash
|
||||
# K8s Agent Proxy 配置
|
||||
K8S_PROXY_TIMEOUT=30.0
|
||||
K8S_PROXY_MAX_RETRIES=3
|
||||
K8S_PROXY_RETRY_DELAY=1.0
|
||||
K8S_PROXY_HEALTH_CHECK_INTERVAL=30
|
||||
```
|
||||
|
||||
### 5.2 应用配置
|
||||
|
||||
在 `config.py` 中添加:
|
||||
|
||||
```python
|
||||
class K8sProxyConfig:
|
||||
timeout: float = float(os.getenv("K8S_PROXY_TIMEOUT", "30.0"))
|
||||
max_retries: int = int(os.getenv("K8S_PROXY_MAX_RETRIES", "3"))
|
||||
retry_delay: float = float(os.getenv("K8S_PROXY_RETRY_DELAY", "1.0"))
|
||||
health_check_interval: int = int(os.getenv("K8S_PROXY_HEALTH_CHECK_INTERVAL", "30"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施计划
|
||||
|
||||
### 6.1 任务清单
|
||||
|
||||
- [ ] **Phase 1: 基础设施**
|
||||
- [ ] 创建 `k8s_agent_proxy.py` 模块
|
||||
- [ ] 添加配置项
|
||||
- [ ] 编写单元测试
|
||||
|
||||
- [ ] **Phase 2: 路由逻辑**
|
||||
- [ ] 修改 `execute_agent` 端点
|
||||
- [ ] 实现路由决策逻辑
|
||||
- [ ] 添加本地执行回退
|
||||
|
||||
- [ ] **Phase 3: 监控和日志**
|
||||
- [ ] 添加执行位置记录
|
||||
- [ ] 添加 Prometheus 指标
|
||||
- [ ] 完善日志记录
|
||||
|
||||
- [ ] **Phase 4: 健康检查**
|
||||
- [ ] 实现 Pod 健康检查
|
||||
- [ ] 添加自动故障转移
|
||||
- [ ] 实现连接池管理
|
||||
|
||||
- [ ] **Phase 5: 测试和文档**
|
||||
- [ ] 集成测试
|
||||
- [ ] 性能测试
|
||||
- [ ] 更新 API 文档
|
||||
|
||||
### 6.2 文件变更清单
|
||||
|
||||
| 文件 | 操作 | 描述 |
|
||||
|-----|------|------|
|
||||
| `services/mcp-server/app/k8s_agent_proxy.py` | 新增 | K8s Agent 代理模块 |
|
||||
| `services/mcp-server/app/routes/agents.py` | 修改 | 添加路由逻辑 |
|
||||
| `services/mcp-server/config.py` | 修改 | 添加代理配置 |
|
||||
| `services/mcp-server/app/lifecycle.py` | 修改 | 添加代理生命周期管理 |
|
||||
| `services/mcp-server/models.py` | 修改 | 添加执行位置字段 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
### 7.1 错误场景和处理策略
|
||||
|
||||
| 场景 | 处理策略 |
|
||||
|-----|---------|
|
||||
| Pod 不可达 | 重试 3 次后返回错误 |
|
||||
| Pod 返回 5xx | 记录错误,返回给用户 |
|
||||
| 请求超时 | 返回超时错误,建议重试 |
|
||||
| Pod 状态非 Running | 回退到本地执行或返回错误 |
|
||||
| access_url 为空 | 使用本地执行 |
|
||||
|
||||
### 7.2 故障转移策略
|
||||
|
||||
```python
|
||||
async def execute_with_fallback(agent, request, ...):
|
||||
"""带故障转移的执行"""
|
||||
|
||||
# 1. 尝试 K8s Pod 执行
|
||||
if agent.access_url and agent.k8s_status == "Running":
|
||||
result = await _execute_on_k8s_pod(...)
|
||||
if result.success:
|
||||
return result
|
||||
|
||||
# 2. K8s 执行失败,检查是否可以本地执行
|
||||
if agent.tools and not agent.template:
|
||||
logger.warning("K8s 执行失败,回退到本地执行")
|
||||
return await _execute_locally(...)
|
||||
|
||||
# 3. 本地执行
|
||||
return await _execute_locally(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 监控指标
|
||||
|
||||
### 8.1 新增 Prometheus 指标
|
||||
|
||||
```python
|
||||
# K8s Agent 执行指标
|
||||
k8s_agent_requests_total = Counter(
|
||||
"k8s_agent_requests_total",
|
||||
"Total K8s agent requests",
|
||||
["pod_name", "status"]
|
||||
)
|
||||
|
||||
k8s_agent_request_duration = Histogram(
|
||||
"k8s_agent_request_duration_seconds",
|
||||
"K8s agent request duration",
|
||||
["pod_name"]
|
||||
)
|
||||
|
||||
k8s_agent_health_status = Gauge(
|
||||
"k8s_agent_health_status",
|
||||
"K8s agent health status",
|
||||
["pod_name"]
|
||||
)
|
||||
```
|
||||
|
||||
### 8.2 日志格式
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"level": "INFO",
|
||||
"message": "转发请求到 AKS Agent",
|
||||
"execution_id": "exec-uuid",
|
||||
"agent_id": "agent-uuid",
|
||||
"pod_name": "my-agent-abc123",
|
||||
"access_url": "http://my-agent.ai-agents.svc.cluster.local:8080",
|
||||
"method": "tools/call",
|
||||
"execution_location": "k8s"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 安全考虑
|
||||
|
||||
### 9.1 网络安全
|
||||
|
||||
- Pod 间通信使用 K8s 内部网络
|
||||
- 不暴露 Pod 到公网
|
||||
- 使用 NetworkPolicy 限制访问
|
||||
|
||||
### 9.2 认证授权
|
||||
|
||||
- 请求中携带 `user_id` 和 `execution_id`
|
||||
- Pod 可验证请求来源
|
||||
- 支持 mTLS(可选)
|
||||
|
||||
### 9.3 数据安全
|
||||
|
||||
- 敏感数据不在日志中记录
|
||||
- 请求/响应数据加密传输
|
||||
- 执行结果脱敏存储
|
||||
|
||||
---
|
||||
|
||||
## 10. 总结
|
||||
|
||||
本方案实现了 MCP Server 与 AKS Agent Pod 的集成,主要特点:
|
||||
|
||||
1. **智能路由**:根据 Agent 配置自动选择执行位置
|
||||
2. **故障转移**:K8s 执行失败时可回退到本地
|
||||
3. **可观测性**:完整的日志、指标和追踪
|
||||
4. **安全性**:网络隔离和认证机制
|
||||
5. **可扩展性**:支持多 Pod 负载均衡(未来)
|
||||
|
||||
通过此方案,用户可以透明地使用部署在 AKS 中的 Agent,无需关心底层执行细节。
|
||||
@@ -0,0 +1,166 @@
|
||||
# 数据库迁移计划:postgres → taiji
|
||||
|
||||
**创建时间**: 2025-12-31
|
||||
**目标**: 将 postgres 库的表结构和数据完全覆盖到 taiji 库
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
将 Azure PostgreSQL 服务器上的 `postgres` 数据库(新结构)完全复制到 `taiji` 数据库(旧结构),包括:
|
||||
- 表结构
|
||||
- 索引
|
||||
- 约束
|
||||
- 数据
|
||||
- 序列
|
||||
|
||||
---
|
||||
|
||||
## 🔄 迁移流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[开始迁移] --> B[连接 postgres 源数据库]
|
||||
B --> C[连接 taiji 目标数据库]
|
||||
C --> D[备份 taiji 数据库 - 可选]
|
||||
D --> E[删除 taiji 中的所有表]
|
||||
E --> F[从 postgres 获取表结构]
|
||||
F --> G[在 taiji 中创建表]
|
||||
G --> H[创建索引和约束]
|
||||
H --> I[复制数据]
|
||||
I --> J[同步序列值]
|
||||
J --> K[验证迁移结果]
|
||||
K --> L[完成]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 任务清单
|
||||
|
||||
### 1. 准备工作
|
||||
- [ ] 确认数据库连接信息正确
|
||||
- [ ] 确认 postgres 库中有最新的表结构
|
||||
- [ ] 备份 taiji 库现有数据(可选但推荐)
|
||||
|
||||
### 2. 创建迁移脚本
|
||||
- [ ] 修改现有 `copy_database.py` 脚本,交换源和目标数据库
|
||||
- [ ] 或创建新脚本 `sync_postgres_to_taiji.py`
|
||||
|
||||
### 3. 脚本功能实现
|
||||
- [ ] 连接源数据库(postgres)
|
||||
- [ ] 连接目标数据库(taiji)
|
||||
- [ ] 获取 postgres 库所有表列表
|
||||
- [ ] 删除 taiji 库中的所有现有表(CASCADE)
|
||||
- [ ] 复制表结构(DDL)
|
||||
- [ ] 复制索引定义
|
||||
- [ ] 复制数据
|
||||
- [ ] 同步序列值
|
||||
|
||||
### 4. 验证和测试
|
||||
- [ ] 验证表数量一致
|
||||
- [ ] 验证数据行数一致
|
||||
- [ ] 验证索引创建成功
|
||||
- [ ] 测试应用连接 taiji 库正常工作
|
||||
|
||||
---
|
||||
|
||||
## 📝 技术细节
|
||||
|
||||
### 数据库连接信息
|
||||
|
||||
```python
|
||||
DB_HOST = "taijipda.postgres.database.azure.com"
|
||||
DB_USER = "taiji"
|
||||
DB_PASSWORD = "By@123456."
|
||||
DB_PORT = 5432
|
||||
|
||||
# 源数据库(新结构)
|
||||
SOURCE_DB = "postgres"
|
||||
|
||||
# 目标数据库(需要更新)
|
||||
TARGET_DB = "taiji"
|
||||
```
|
||||
|
||||
### 主要表列表(基于 models.py)
|
||||
|
||||
| 表名 | 说明 |
|
||||
|------|------|
|
||||
| users | 用户表(租户使用者) |
|
||||
| agents | Agent表(平台Agent和自定义Agent) |
|
||||
| tools | 工具表 |
|
||||
| sessions | 会话表 |
|
||||
| executions | 执行记录表 |
|
||||
| api_keys | API密钥表 |
|
||||
| billing | 计费详情表 |
|
||||
| balances | 用户余额表 |
|
||||
| billing_records | 计费记录表 |
|
||||
| channels | 渠道合作伙伴表 |
|
||||
| model_providers | 模型供应商表 |
|
||||
| resource_allocations | 资源分配表 |
|
||||
| applications | 申请审批表 |
|
||||
| workflows | 工作流表 |
|
||||
| audit_logs | 审计日志表 |
|
||||
| channel_agent_quotas | 渠道Agent配额表 |
|
||||
| provider_models | 模型提供商表 |
|
||||
| token_blacklist | Token黑名单表 |
|
||||
| resource_usage | 资源使用记录表 |
|
||||
| quota_alerts | 配额预警记录表 |
|
||||
| model_pricing | 模型定价配置表 |
|
||||
| provider_health_checks | 供应商健康检查记录表 |
|
||||
| agent_traces | Agent执行轨迹表 |
|
||||
| billing_events | 计费事件表 |
|
||||
| channel_provider_access | 渠道供应商授权表 |
|
||||
| provider_applications | 供应商使用申请表 |
|
||||
| gateway_apis | 网关API定义表 |
|
||||
| data_templates | 数据模板表 |
|
||||
| recharge_records | 充值记录表 |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **数据丢失风险**: 此操作会删除 taiji 库中的所有现有数据,请确保已备份
|
||||
2. **外键约束**: 删除表时使用 CASCADE 处理外键依赖
|
||||
3. **序列同步**: 确保序列值正确同步,避免主键冲突
|
||||
4. **连接中断**: 迁移过程中确保网络稳定
|
||||
5. **应用停机**: 建议在迁移期间停止连接 taiji 库的应用服务
|
||||
|
||||
---
|
||||
|
||||
## 🚀 执行步骤
|
||||
|
||||
1. **运行迁移脚本**:
|
||||
```bash
|
||||
cd /home/taiji/tools/taiji-AI-PAD
|
||||
python scripts/sync_postgres_to_taiji.py
|
||||
```
|
||||
|
||||
2. **验证迁移结果**:
|
||||
```bash
|
||||
# 连接 taiji 库检查表
|
||||
psql "host=taijipda.postgres.database.azure.com port=5432 dbname=taiji user=taiji password=By@123456. sslmode=require"
|
||||
|
||||
# 查看所有表
|
||||
\dt
|
||||
|
||||
# 检查数据行数
|
||||
SELECT COUNT(*) FROM users;
|
||||
SELECT COUNT(*) FROM agents;
|
||||
```
|
||||
|
||||
3. **更新应用配置**(如需要):
|
||||
确保应用的 DATABASE_URL 指向 taiji 库
|
||||
|
||||
---
|
||||
|
||||
## 📊 预期结果
|
||||
|
||||
迁移完成后:
|
||||
- taiji 库将拥有与 postgres 库完全相同的表结构
|
||||
- 所有数据将从 postgres 库复制到 taiji 库
|
||||
- 索引和约束将正确创建
|
||||
- 序列值将同步
|
||||
|
||||
---
|
||||
|
||||
**下一步**: 切换到 Code 模式创建迁移脚本
|
||||
Reference in New Issue
Block a user