Update Heicode sub-mode runtime changes

This commit is contained in:
elipitc
2026-05-31 18:00:17 +08:00
parent f8464fe606
commit ffed09647a
154 changed files with 16388 additions and 444 deletions
+683
View File
@@ -0,0 +1,683 @@
# AI Agent 功能迁移计划
## 项目背景
将 AIExamPlatform 中的 AI agent 问答功能迁移到 AgentAPI 微服务架构中。
**源项目**:`/Users/mac/Projects/AIExamPlatform/AIExamPlatform/app`
**目标项目**:`/Users/mac/Projects/AIExamPlatform/AgentAPI`
## 核心需求优先级
### P0 - 最高优先级(本计划重点)
集成 `questionagent` 的答案增强功能:
- 传入题目信息(题干、选项、正确答案)
- 传入 AI 生成的答案和参考答案
- 调用 `questionagent` 进行增强知识问答
- 返回增强后的答案(包含教材知识点、解题策略、可视化建议等)
### P1 - 较低优先级(后续实现)
- 异步题目导入功能
- 导入过程中自动调用 AI agents 生成答案
---
## 一、迁移范围分析
### 1.1 核心功能模块
#### ✅ 已存在于 AgentAPI
- **questionagent 子模块**:`/Users/mac/Projects/AIExamPlatform/AgentAPI/agentapi/external/questionagent`
- `TeachingVisualAgent`:教学可视化 agent
- `AnswerEnhancer`:答案增强器(核心功能)
- `MinerUDocumentExplorerSkill`:教材知识点查询
- `ProblemAnalyzer`:题目分析器
- `SolverRegistry`:解题器注册表
#### 🔄 需要适配的功能
从源项目迁移以下 agent 功能(作为参考,但核心使用 questionagent):
- **ConversationAgent**:对话式学习(多轮对话、记忆管理)
- **QuestionChatAgent**:题目对话(技能系统、意图识别)
- **ExplanationAgent**:题目解析生成
- **SimilarityAgent**:相似题目查找(基于标签的规则匹配)
### 1.2 依赖分析
#### 当前 AgentAPI 依赖
```toml
fastapi>=0.135.3
sqlalchemy>=2.0.49
pydantic>=2.12.5
uvicorn[standard]>=0.44.0
```
#### 需要新增的依赖
```toml
# LangChain 生态
langchain>=0.3.25
langchain-openai>=0.3.16
langchain-mcp-adapters>=0.1.7
# OpenAI / Anthropic
openai>=1.76.0
anthropic>=0.94.0 # 可选,如果需要 Claude
# MCP 协议
mcp>=1.18.0
# 其他工具
pillow>=11.2.0 # 图像处理
pyyaml>=6.0.2 # 配置文件
```
---
## 二、架构设计
### 2.1 目录结构
```
AgentAPI/agentapi/
├── external/
│ └── questionagent/ # 已存在的 git submodule
│ ├── src/agent/ # Agent 运行时
│ └── src/teaching_visual_mcp/ # MCP 工具
├── services/
│ ├── chat_service.py # 已存在
│ ├── agent_service.py # 新增:Agent 服务层
│ └── answer_enhancement_service.py # 新增:答案增强服务
├── repositories/
│ ├── chat_repository.py # 已存在
│ └── agent_session_repository.py # 新增:Agent 会话持久化
├── models/
│ ├── chat.py # 已存在
│ ├── question.py # 已存在
│ └── agent_session.py # 新增:Agent 会话模型
├── http/routers/
│ ├── chat.py # 已存在
│ └── agents.py # 新增:Agent API 路由
└── schemas/
└── agent_schemas.py # 新增:Agent 请求/响应模型
```
### 2.2 数据模型设计
#### AgentSession(新增)
```python
class AgentSession(Base):
__tablename__ = "agent_sessions"
id: Mapped[int]
user_id: Mapped[str]
question_id: Mapped[int | None]
agent_type: Mapped[str] # "answer_enhancement", "conversation", "question_chat"
status: Mapped[str] # "active", "completed", "failed"
metadata: Mapped[dict] # JSON 字段存储 agent 特定数据
created_at: Mapped[datetime]
updated_at: Mapped[datetime]
```
#### AgentMessage(新增)
```python
class AgentMessage(Base):
__tablename__ = "agent_messages"
id: Mapped[int]
session_id: Mapped[int]
role: Mapped[str] # "user", "assistant", "system"
content: Mapped[str]
metadata: Mapped[dict | None] # 存储技能使用、工具调用等信息
created_at: Mapped[datetime]
```
#### QuestionAnswer 扩展(已存在,需要利用)
```python
# 已有字段:
# - answer_source: "official", "ai_generated", "ai_enhanced"
# - content_markdown: 答案内容
# - version_no: 版本号
```
---
## 三、详细实施步骤
### 步骤 1:环境准备与依赖安装
**目标**:安装必要的依赖,确保 questionagent 子模块可用
**操作**:
```bash
cd /Users/mac/Projects/AIExamPlatform/AgentAPI
# 添加 LangChain 和 AI 相关依赖
uv add "langchain>=0.3.25"
uv add "langchain-openai>=0.3.16"
uv add "langchain-mcp-adapters>=0.1.7"
uv add "openai>=1.76.0"
uv add "mcp>=1.18.0"
uv add "pillow>=11.2.0"
uv add "pyyaml>=6.0.2"
# 可选:如果需要 Claude
uv add "anthropic>=0.94.0"
# 同步环境
uv sync
```
**验收标准**:
- ✅ `uv.lock` 更新成功
- ✅ 所有依赖安装无冲突
- ✅ 可以成功 `from agent.runtime import TeachingVisualAgent`
---
### 步骤 2:创建 Agent 服务层
**目标**:封装 questionagent 的答案增强功能为 AgentAPI 的服务层
**文件**:`agentapi/services/answer_enhancement_service.py`
**核心功能**:
```python
class AnswerEnhancementService:
"""答案增强服务
封装 questionagent 的 AnswerEnhancer,提供:
1. 题目分析
2. 教材知识点查询
3. 答案策略生成
4. 可视化建议
"""
def __init__(self):
# 初始化 questionagent 组件
self.agent_settings = AgentSettings()
self.mineru_skill = MinerUDocumentExplorerSkill(...)
self.answer_enhancer = AnswerEnhancer(
mineru_skill=self.mineru_skill,
analyzer=ProblemAnalyzer(),
solver_registry=build_default_solver_registry(),
)
def enhance_answer(
self,
question_id: int,
question_text: str,
ai_answer: str | None,
reference_answer: str | None,
subject_hint: str | None = None,
topic_hint: str | None = None,
) -> AnswerEnhancementResult:
"""增强答案
Args:
question_id: 题目 ID
question_text: 题目文本(题干 + 选项)
ai_answer: AI 生成的答案
reference_answer: 参考答案
subject_hint: 科目提示
topic_hint: 主题提示
Returns:
增强后的答案结果
"""
request = AnswerEnhancementRequest(
question=question_text,
subject_hint=subject_hint,
topic_hint=topic_hint,
include_visual_plan=True,
)
result = self.answer_enhancer.enhance_answer(request)
return result
```
**验收标准**:
- ✅ 服务类可以成功初始化
- ✅ `enhance_answer` 方法可以调用 questionagent
- ✅ 返回结构化的增强结果
---
### 步骤 3:创建数据库模型和 Repository
**目标**:持久化 Agent 会话和消息
**文件**:
- `agentapi/models/agent_session.py`
- `agentapi/repositories/agent_session_repository.py`
**核心功能**:
```python
# Repository
class AgentSessionRepository:
def create_session(
self,
user_id: str,
question_id: int | None,
agent_type: str,
) -> AgentSession:
"""创建 Agent 会话"""
def add_message(
self,
session_id: int,
role: str,
content: str,
metadata: dict | None = None,
) -> AgentMessage:
"""添加消息到会话"""
def get_session_history(
self,
session_id: int,
) -> list[AgentMessage]:
"""获取会话历史"""
```
**验收标准**:
- ✅ 数据库迁移脚本生成成功
- ✅ 可以创建和查询 Agent 会话
- ✅ 消息历史正确存储和检索
---
### 步骤 4:创建 API 路由
**目标**:暴露答案增强功能为 RESTful API
**文件**:`agentapi/http/routers/agents.py`
**核心端点**:
#### 4.1 答案增强 API
```python
@router.post("/answer-enhancement")
async def enhance_answer(
request: AnswerEnhancementRequest,
db: Session = Depends(get_db),
) -> AnswerEnhancementResponse:
"""增强答案
请求示例:
{
"question_id": 123,
"subject_hint": "信号与系统",
"topic_hint": "卷积",
"include_visual_plan": true
}
响应示例:
{
"question_id": 123,
"subject": "信号与系统",
"topic": "卷积运算",
"knowledge_points": [...],
"key_points": ["理解卷积定义", "掌握图解法"],
"answer_strategy": [
{"title": "步骤1", "detail": "..."},
{"title": "步骤2", "detail": "..."}
],
"answer_draft": "完整答案文本...",
"visual_plan": {...},
"study_advice": [...]
}
"""
```
#### 4.2 Agent 会话 API(可选,用于多轮对话)
```python
@router.post("/sessions")
async def create_agent_session(
request: CreateSessionRequest,
db: Session = Depends(get_db),
) -> SessionResponse:
"""创建 Agent 会话"""
@router.post("/sessions/{session_id}/messages")
async def send_message(
session_id: int,
request: SendMessageRequest,
db: Session = Depends(get_db),
) -> MessageResponse:
"""发送消息到 Agent 会话"""
```
**验收标准**:
- ✅ API 端点可以正常访问
- ✅ 请求验证正确(Pydantic)
- ✅ 返回结构化的增强结果
- ✅ 错误处理完善(404, 500 等)
---
### 步骤 5:集成到现有 Question 流程
**目标**:将答案增强功能集成到题目答案生成流程
**文件**:`agentapi/services/question_service.py`(扩展现有服务)
**核心功能**:
```python
class QuestionService:
@staticmethod
def generate_enhanced_answer(
db: Session,
question_id: int,
user_id: str,
) -> QuestionAnswer:
"""为题目生成增强答案
流程:
1. 查询题目信息(题干、选项、正确答案)
2. 调用 AnswerEnhancementService
3. 将增强结果保存为 QuestionAnswer(answer_source="ai_enhanced")
4. 返回答案记录
"""
# 1. 查询题目
question_repo = QuestionRepository(db)
question = question_repo.get_question_with_details(question_id)
# 2. 构建题目文本
question_text = _build_question_text(question)
# 3. 调用答案增强服务
enhancement_service = AnswerEnhancementService()
result = enhancement_service.enhance_answer(
question_id=question_id,
question_text=question_text,
ai_answer=None, # 可选:如果已有 AI 答案
reference_answer=_get_official_answer(question),
subject_hint=_infer_subject(question),
topic_hint=None,
)
# 4. 保存增强答案
answer = question_repo.create_answer(
question_id=question_id,
answer_source="ai_enhanced",
content_markdown=result.answer_draft,
metadata={
"subject": result.subject,
"topic": result.topic,
"key_points": result.key_points,
"answer_strategy": [s.model_dump() for s in result.answer_strategy],
"visual_plan": result.visual_plan,
"study_advice": result.study_advice,
}
)
db.commit()
return answer
```
**验收标准**:
- ✅ 可以为题目生成增强答案
- ✅ 答案正确保存到数据库
- ✅ metadata 字段包含完整的增强信息
- ✅ 可以查询和展示增强答案
---
### 步骤 6:配置和环境变量
**目标**:配置 OpenAI API、MinerU 等外部服务
**文件**:`agentapi/config.py`(扩展现有配置)
**新增配置**:
```python
class Settings(BaseSettings):
# ... 现有配置 ...
# OpenAI 配置
openai_api_key: str | None = None
openai_base_url: str | None = None
openai_agent_model: str = "gpt-4.1-mini"
# Agent 配置
agent_temperature: float = 0.0
agent_max_iterations: int = 8
# MinerU 配置
mineru_qmd_command: str = "qmd"
mineru_default_collection: str = "textbooks"
mineru_lookup_mode: Literal["search", "query"] = "query"
# 教学可视化配置
teaching_visual_artifact_root: Path = Path(".artifacts/teaching-visuals")
```
**环境变量示例**(`.env`):
```bash
# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_AGENT_MODEL=gpt-4.1-mini
# MinerU(可选,如果需要教材查询)
TVAGENT_MINERU_DEFAULT_COLLECTION=textbooks
TVAGENT_MINERU_LOOKUP_MODE=query
```
**验收标准**:
- ✅ 配置可以从环境变量加载
- ✅ OpenAI API 密钥正确配置
- ✅ Agent 可以成功调用 OpenAI
---
## 四、测试计划
### 4.1 单元测试
**文件**:`tests/services/test_answer_enhancement_service.py`
```python
def test_enhance_answer_basic():
"""测试基本答案增强功能"""
service = AnswerEnhancementService()
result = service.enhance_answer(
question_id=1,
question_text="求信号 x(t) 和 h(t) 的卷积...",
ai_answer=None,
reference_answer="y(t) = ...",
subject_hint="信号与系统",
)
assert result.subject == "信号与系统"
assert len(result.key_points) > 0
assert len(result.answer_strategy) > 0
assert result.answer_draft is not None
```
### 4.2 集成测试
**文件**:`tests/http/test_agents_router.py`
```python
def test_answer_enhancement_api(client: TestClient, db: Session):
"""测试答案增强 API"""
# 1. 创建测试题目
question = create_test_question(db)
# 2. 调用答案增强 API
response = client.post(
"/api/v1/agents/answer-enhancement",
json={
"question_id": question.id,
"subject_hint": "信号与系统",
"include_visual_plan": True,
}
)
assert response.status_code == 200
data = response.json()
assert data["question_id"] == question.id
assert "key_points" in data
assert "answer_strategy" in data
```
### 4.3 端到端测试
**手动测试流程**:
1. 启动 AgentAPI 服务
2. 使用 Postman/curl 调用答案增强 API
3. 验证返回的增强答案质量
4. 检查数据库中的答案记录
---
## 五、迁移优先级和时间估算
| 步骤 | 优先级 | 预估时间 | 依赖 |
|------|--------|----------|------|
| 步骤 1:依赖安装 | P0 | 0.5h | 无 |
| 步骤 2:服务层 | P0 | 2h | 步骤 1 |
| 步骤 3:数据模型 | P0 | 1.5h | 步骤 1 |
| 步骤 4:API 路由 | P0 | 2h | 步骤 2, 3 |
| 步骤 5:集成到 Question | P0 | 1.5h | 步骤 2, 3, 4 |
| 步骤 6:配置 | P0 | 0.5h | 步骤 1 |
| 测试 | P0 | 2h | 所有步骤 |
**总计**:约 10 小时(1-2 个工作日)
---
## 六、风险和注意事项
### 6.1 技术风险
1. **OpenAI API 调用失败**
- 风险:API 密钥无效、配额不足、网络问题
- 缓解:实现降级策略(本地 fallback)、错误重试、详细日志
2. **MinerU 教材查询依赖**
- 风险:`qmd` 命令不可用、教材集合未配置
- 缓解:使 MinerU 功能可选,提供 mock 数据用于测试
3. **性能问题**
- 风险:LLM 调用耗时长(5-30秒)
- 缓解:实现异步处理、添加超时控制、考虑缓存策略
### 6.2 数据一致性
1. **答案版本管理**
- 问题:同一题目可能有多个 AI 生成的答案版本
- 方案:利用 `QuestionAnswer.version_no` 和 `is_latest` 字段
2. **元数据存储**
- 问题:增强结果包含复杂的嵌套结构
- 方案:使用 JSON 字段存储 metadata,或考虑单独的表
### 6.3 兼容性
1. **questionagent 子模块更新**
- 问题:外部子模块更新可能破坏兼容性
- 方案:锁定子模块版本、编写适配层、充分测试
2. **Python 版本要求**
- 问题:questionagent 要求 Python >=3.11,AgentAPI 要求 >=3.12
- 方案:已兼容,无问题
---
## 七、后续扩展(P1 优先级)
### 7.1 异步题目导入
**功能**:
- 批量导入题目时,自动调用 AI agents 生成答案
- 使用 Celery 或 FastAPI BackgroundTasks 实现异步处理
**架构**:
```python
# 任务队列
@celery_app.task
def generate_answer_for_question(question_id: int):
"""异步生成题目答案"""
db = SessionLocal()
try:
QuestionService.generate_enhanced_answer(db, question_id, "system")
finally:
db.close()
# 导入流程
def import_questions_batch(questions: list[dict]):
"""批量导入题目"""
for q_data in questions:
# 1. 创建题目记录
question = create_question(q_data)
# 2. 异步生成答案
generate_answer_for_question.delay(question.id)
```
### 7.2 其他 Agent 功能
- **ConversationAgent**:对话式学习(多轮对话)
- **SimilarityAgent**:相似题目推荐
- **QuestionChatAgent**:题目对话(技能系统)
---
## 八、成功标准
### 核心功能验收
- ✅ 可以通过 API 调用答案增强功能
- ✅ 增强答案包含教材知识点、解题策略、可视化建议
- ✅ 答案正确保存到数据库
- ✅ 性能可接受(单次调用 < 30秒)
### 代码质量
- ✅ 代码符合 AgentAPI 架构规范(services/repositories/models/routers)
- ✅ 类型注解完整(Python 3.12+ typing)
- ✅ 错误处理完善
- ✅ 日志记录清晰
### 文档和测试
- ✅ API 文档完整(FastAPI 自动生成)
- ✅ 单元测试覆盖核心逻辑
- ✅ 集成测试验证端到端流程
- ✅ README 包含使用说明和配置指南
---
## 九、开放问题
以下问题需要在实施过程中明确:
1. **教材集合配置**
- 是否已有 MinerU 教材集合?
- 教材数据存储在哪里?
- 如何配置 `qmd` 命令?
2. **OpenAI API 配置**
- 使用哪个 OpenAI 模型?(gpt-4.1-mini, gpt-4o, etc.)
- API 密钥如何管理?(环境变量、密钥管理服务)
- 是否需要支持其他 LLM 提供商(Claude, 本地模型)?
3. **答案展示**
- 前端如何展示增强答案?
- 是否需要支持 Markdown 渲染?
- 可视化建议如何展示?
4. **性能优化**
- 是否需要缓存增强结果?
- 是否需要异步处理?
- 是否需要限流?
5. **用户权限**
- 哪些用户可以调用答案增强功能?
- 是否需要计费或配额限制?
---
## 十、参考资料
- **questionagent README**:`/Users/mac/Projects/AIExamPlatform/AgentAPI/agentapi/external/questionagent/README.md`
- **AgentAPI 架构**:`/Users/mac/Projects/AIExamPlatform/AgentAPI/docs/README.md`
- **LangChain 文档**:https://python.langchain.com/
- **MCP 协议**:https://modelcontextprotocol.io/
+924
View File
@@ -0,0 +1,924 @@
# Heicode Integration - Implementation Plan
**Version**: 1.0
**Date**: 2026-05-08
**Based on**:
- Agent-Manager-Heicode对接需求文档(2).md v1.1
- heicode-integration-plan.md
- Analyst review findings
---
## Implementation Strategy
This plan implements the Heicode integration in 6 phases, starting with Phase 1 (Foundation & Authentication) as requested by the user. The implementation will be **fully incremental** - all new code under `/api/agnet/*` with zero changes to existing `/agents/*`, `/templates/*` endpoints.
---
## Phase 1: Foundation & Authentication (Days 1-3)
### 1.1 Project Structure Setup
**Files to create**:
```
api/
├── __init__.py
├── agnet/
│ ├── __init__.py
│ ├── router.py # Main FastAPI router
│ ├── models.py # Pydantic request/response models
│ ├── auth.py # Service token middleware
│ ├── dependencies.py # FastAPI dependencies
│ └── validators.py # Request validation logic
config/
├── __init__.py
├── settings.py # Pydantic settings (env vars)
└── error_codes.py # Error code enums
```
**Implementation**:
1. **Create `config/error_codes.py`**:
```python
from enum import Enum
class ErrorCode(str, Enum):
# Authentication
UNAUTHORIZED = "UNAUTHORIZED"
INVALID_TOKEN = "INVALID_TOKEN"
# Validation
POLICY_REJECTED = "POLICY_REJECTED"
RESOURCE_GRANT_SECRET_REJECTED = "RESOURCE_GRANT_SECRET_REJECTED"
MODEL_NOT_ALLOWED = "MODEL_NOT_ALLOWED"
# Resource limits
BUDGET_EXCEEDED = "BUDGET_EXCEEDED"
# State conflicts
DEPLOYMENT_NOT_FOUND = "DEPLOYMENT_NOT_FOUND"
DEPLOYMENT_CONFLICT = "DEPLOYMENT_CONFLICT"
# Infrastructure
INTERNAL_ERROR = "INTERNAL_ERROR"
```
2. **Create `config/settings.py`**:
```python
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# Service token (Phase 1-4: pre-shared)
HEICODE_SERVICE_TOKEN: str
# Database
DATABASE_URL: str = "sqlite:///./agent_manager.db"
# Redis (for idempotency)
REDIS_URL: str = "redis://localhost:6379/0"
IDEMPOTENCY_TTL_SECONDS: int = 86400 # 24 hours
# Kubernetes
NAMESPACE_PREFIX: str = "agnet"
# Model gateways
HEICODE_NEWAPI_BASE_URL: str = "https://code.xinghanlab.com"
LITELLM_BASE_URL: str = "http://litellm-service:8000"
# Limits
MAX_PAYLOAD_SIZE_MB: int = 1
MAX_CONCURRENT_DEPLOYMENTS_PER_USER: int = 10
MAX_CONCURRENT_DEPLOYMENTS_PER_SCOPE: int = 50
class Config:
env_file = ".env"
settings = Settings()
```
3. **Create `api/agnet/auth.py`** (Service token middleware):
```python
from fastapi import Request, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from config.settings import settings
from config.error_codes import ErrorCode
import logging
logger = logging.getLogger(__name__)
security = HTTPBearer()
async def verify_service_token(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> str:
"""Verify service token from mcp-server."""
token = credentials.credentials
# Phase 1-4: Simple pre-shared token validation
if token != settings.HEICODE_SERVICE_TOKEN:
logger.warning(f"Invalid service token attempt")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail={
"success": False,
"error": {
"code": ErrorCode.INVALID_TOKEN,
"message": "Invalid service token",
"request_id": None
}
}
)
return token
def extract_headers(request: Request) -> dict:
"""Extract required headers for correlation and audit."""
return {
"correlation_id": request.headers.get("X-Correlation-Id"),
"user_id": request.headers.get("X-User-Id"),
"binding_scope": request.headers.get("X-Binding-Scope"),
"idempotency_key": request.headers.get("Idempotency-Key"),
}
```
4. **Create `api/agnet/models.py`** (Pydantic models - Phase 1 subset):
```python
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from datetime import datetime
from enum import Enum
class BillingProvider(str, Enum):
NEWAPI = "newapi"
LITELLM = "litellm"
class RiskLevel(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class ErrorResponse(BaseModel):
success: bool = False
error: Dict[str, Any]
class SuccessResponse(BaseModel):
success: bool = True
data: Dict[str, Any]
# More models will be added in Phase 2
```
5. **Create `api/agnet/validators.py`** (Sensitive field scanner):
```python
import re
from typing import Any, Dict, List
from config.error_codes import ErrorCode
from fastapi import HTTPException
SENSITIVE_KEYWORDS = [
"password", "passwd", "pwd",
"token", "bearer",
"secret", "api_key", "apikey",
"private_key", "privatekey",
"access_key", "accesskey",
"credential", "auth"
]
def scan_for_sensitive_fields(data: Any, path: str = "") -> List[str]:
"""Recursively scan for sensitive field names."""
violations = []
if isinstance(data, dict):
for key, value in data.items():
current_path = f"{path}.{key}" if path else key
key_lower = key.lower()
# Check if key contains sensitive keywords
if any(keyword in key_lower for keyword in SENSITIVE_KEYWORDS):
violations.append(current_path)
# Recurse into nested structures
violations.extend(scan_for_sensitive_fields(value, current_path))
elif isinstance(data, list):
for i, item in enumerate(data):
violations.extend(scan_for_sensitive_fields(item, f"{path}[{i}]"))
return violations
def validate_no_sensitive_fields(payload: Dict[str, Any]) -> None:
"""Validate that payload doesn't contain sensitive fields."""
violations = scan_for_sensitive_fields(payload)
if violations:
raise HTTPException(
status_code=422,
detail={
"success": False,
"error": {
"code": ErrorCode.RESOURCE_GRANT_SECRET_REJECTED,
"message": f"Request contains sensitive fields: {', '.join(violations[:5])}",
"details": {"violations": violations}
}
}
)
```
6. **Create `api/agnet/router.py`** (Main router with health check):
```python
from fastapi import APIRouter, Depends, Request
from api.agnet.auth import verify_service_token, extract_headers
from api.agnet.models import SuccessResponse
import logging
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/api/agnet",
tags=["agnet"],
dependencies=[Depends(verify_service_token)]
)
@router.get("/health", response_model=SuccessResponse)
async def health_check(request: Request):
"""Health check endpoint for Heicode integration."""
headers = extract_headers(request)
logger.info(f"Health check - correlation_id={headers['correlation_id']}")
return {
"success": True,
"data": {
"status": "healthy",
"service": "agent-manager-agnet",
"version": "1.0.0"
}
}
```
7. **Update `app.py`** to include new router:
```python
# Add at top with other imports
from api.agnet.router import router as agnet_router
# Add after existing router registrations
app.include_router(agnet_router)
```
### 1.2 Idempotency Support (Redis)
**Files to create**:
```
api/agnet/idempotency.py
```
**Implementation**:
```python
import redis
import json
from typing import Optional, Dict, Any
from config.settings import settings
import logging
logger = logging.getLogger(__name__)
class IdempotencyCache:
def __init__(self):
self.redis_client = redis.from_url(
settings.REDIS_URL,
decode_responses=True
)
def get(self, key: str) -> Optional[Dict[str, Any]]:
"""Get cached response for idempotency key."""
try:
cached = self.redis_client.get(f"idempotency:{key}")
if cached:
return json.loads(cached)
except Exception as e:
logger.error(f"Redis get error: {e}")
return None
def set(self, key: str, response: Dict[str, Any]) -> None:
"""Cache response for idempotency key."""
try:
self.redis_client.setex(
f"idempotency:{key}",
settings.IDEMPOTENCY_TTL_SECONDS,
json.dumps(response)
)
except Exception as e:
logger.error(f"Redis set error: {e}")
idempotency_cache = IdempotencyCache()
```
### 1.3 Testing Phase 1
**Test cases**:
1. **Service token validation**:
- Valid token → 200
- Invalid token → 401 with `INVALID_TOKEN`
- Missing token → 401
2. **Health check**:
- GET /api/agnet/health → 200 with status
3. **Sensitive field scanner**:
- Payload with `password` field → 422 `RESOURCE_GRANT_SECRET_REJECTED`
- Nested sensitive field → 422
- Clean payload → passes
4. **Idempotency cache**:
- Set and retrieve value
- TTL expiration after 24h
**Acceptance criteria**:
- [ ] Service token middleware blocks unauthorized requests
- [ ] Headers (correlation_id, user_id, binding_scope) extracted correctly
- [ ] Sensitive field scanner detects all keywords
- [ ] Redis idempotency cache working
- [ ] Health check endpoint returns 200
---
## Phase 2: Core Deployment Endpoints (Days 4-10)
### 2.1 Database Models
**Files to create**:
```
models/
├── __init__.py
├── deployment.py
├── agent_instance.py
└── base.py
```
**Implementation**:
1. **Extend `database.py`** with new tables:
```python
# Add to existing database.py
class Deployment(Base):
__tablename__ = "deployments"
id = Column(Integer, primary_key=True)
deployment_id = Column(String(100), unique=True, nullable=False, index=True)
# Ownership
user_id = Column(String(100), nullable=False, index=True)
binding_scope = Column(String(200), nullable=False, index=True)
correlation_id = Column(String(100))
# Configuration
orchestration_plan = Column(Text, nullable=False)
risk_level = Column(String(20), nullable=False)
approval_token = Column(Text)
# Budget
budget_usd = Column(Numeric(10, 2))
budget_consumed_usd = Column(Numeric(10, 2), default=0.00)
# Model gateway
billing_provider = Column(String(50), nullable=False) # newapi | litellm
default_model_id = Column(String(200), nullable=False)
allowed_model_ids = Column(JSON, nullable=False)
secret_ref = Column(String(500))
# Resource grants
resource_grants = Column(JSON, default=[])
# Status
status = Column(String(50), nullable=False, default="pending")
phase = Column(String(100))
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
stopped_at = Column(DateTime)
# Relationships
agent_instances = relationship("AgentInstance", back_populates="deployment", cascade="all, delete-orphan")
class AgentInstance(Base):
__tablename__ = "agent_instances"
id = Column(Integer, primary_key=True)
agent_instance_id = Column(String(100), unique=True, nullable=False, index=True)
deployment_id = Column(String(100), ForeignKey("deployments.deployment_id", ondelete="CASCADE"), nullable=False)
# Configuration
role = Column(String(100), nullable=False)
phase = Column(String(100))
# Kubernetes
namespace = Column(String(100), nullable=False)
pod_name = Column(String(100), nullable=False)
service_account = Column(String(100))
configmap_name = Column(String(100))
# Status
status = Column(String(50), nullable=False, default="pending")
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
deployment = relationship("Deployment", back_populates="agent_instances")
```
### 2.2 POST /api/agnet/deployments
**Files to create**:
```
api/agnet/deployments.py
services/deployment_orchestrator.py
```
**Implementation steps**:
1. Define complete Pydantic models in `api/agnet/models.py`
2. Implement validation logic (provider enum, approval check, model_id validation)
3. Implement deployment orchestrator service
4. Create K8s resources (namespace, ServiceAccount, ConfigMap, Deployment)
5. Store deployment in database
6. Return response with deployment_id
**Key validations**:
- `billing_context.provider` ∈ ["newapi", "litellm"]
- `risk_level=high` → `approval_token` required
- `default_model_id` ∈ `allowed_model_ids`
- Sensitive field scan
- Idempotency check
### 2.3 GET /api/agnet/deployments (List)
**Implementation**:
- Query deployments table with filters
- Implement cursor-based pagination
- Return deployment list
### 2.4 GET /api/agnet/deployments/{id} (Details)
**Implementation**:
- Query deployment by deployment_id
- Include agent_instances
- Return full details
### 2.5 POST /api/agnet/deployments/{id}/stop
**Implementation**:
- Validate deployment exists
- Check if already stopped (idempotent)
- Validate approval for high-risk
- Delete K8s Deployment
- Update status to "stopped"
**Acceptance criteria**:
- [ ] POST /api/agnet/deployments creates deployment in database
- [ ] Idempotency: same key returns same deployment_id
- [ ] Sensitive fields rejected
- [ ] Provider validation working
- [ ] GET endpoints return correct data
- [ ] Stop endpoint is idempotent
---
## Phase 3: Observability Endpoints (Days 11-15)
### 3.1 Event and Audit Log Models
**Files to create**:
```
models/event.py
models/audit_log.py
```
### 3.2 Log Redaction Service
**Files to create**:
```
services/log_redactor.py
```
**Implementation**:
```python
import re
from typing import List, Tuple
REDACTION_PATTERNS: List[Tuple[re.Pattern, str]] = [
(re.compile(r'password["\']?\s*[:=]\s*["\']?([^"\'\s]+)', re.I), r'password=***'),
(re.compile(r'token["\']?\s*[:=]\s*["\']?([^"\'\s]+)', re.I), r'token=***'),
(re.compile(r'bearer\s+([A-Za-z0-9\-._~+/]+=*)', re.I), r'bearer ***'),
(re.compile(r'api[_-]?key["\']?\s*[:=]\s*["\']?([^"\'\s]+)', re.I), r'api_key=***'),
(re.compile(r'://([^:]+):([^@]+)@', re.I), r'://\1:***@'), # connection strings
]
def redact_log_message(message: str) -> Tuple[str, bool]:
"""Redact sensitive information from log message.
Returns:
(redacted_message, was_redacted)
"""
redacted = message
was_redacted = False
for pattern, replacement in REDACTION_PATTERNS:
new_message = pattern.sub(replacement, redacted)
if new_message != redacted:
was_redacted = True
redacted = new_message
return redacted, was_redacted
```
### 3.3 Implement Endpoints
1. **GET /api/agnet/deployments/{id}/logs**
- Fetch logs from K8s pods
- Apply redaction
- Return paginated logs
2. **GET /api/agnet/deployments/{id}/events**
- Query events table
- Filter by event_type, time range
- Return paginated events
3. **GET /api/agnet/deployments/{id}/metrics**
- Query K8s metrics API
- Aggregate time-series data
- Return metrics
4. **GET /api/agnet/projects/{binding_scope}/dashboard-snapshot**
- Aggregate across all deployments in scope
- Calculate failure rate, avg duration
- Return snapshot
5. **GET /api/agnet/audit-logs**
- Query audit_logs table
- Filter by user_id, binding_scope, action
- Return paginated logs
**Acceptance criteria**:
- [ ] Log redaction removes all sensitive patterns
- [ ] Logs endpoint returns paginated, redacted logs
- [ ] Events endpoint returns structured events
- [ ] Metrics endpoint returns time-series data
- [ ] Dashboard snapshot aggregates correctly
- [ ] Audit logs queryable by filters
---
## Phase 4: K8s Integration & Pod Startup (Days 16-22)
### 4.1 ConfigMap Generator
**Files to create**:
```
services/configmap_generator.py
```
**Implementation**:
```python
def generate_agent_md(deployment: Deployment, agent_config: dict) -> str:
"""Generate AGENT.md natural language context."""
return f"""# Role: {agent_config['role']}
# Goal: {deployment.orchestration_plan}
# Resources you can use:
{format_resources(deployment.resource_grants)}
# Models: {deployment.default_model_id} (allowed: {', '.join(deployment.allowed_model_ids)})
# Forbidden:
- Accessing resources outside granted permissions
"""
def generate_resource_context(deployment: Deployment, agent_config: dict) -> dict:
"""Generate resource_context.json (metadata, NO secrets)."""
return {
"agent_role": agent_config['role'],
"deployment_id": deployment.deployment_id,
"resources": [
{
"resource_id": grant['resource_id'],
"type": grant['resource_type'],
"secret_ref": grant['secret_ref'], # Reference only, not actual secret
"constraints": grant.get('constraints', {})
}
for grant in deployment.resource_grants
]
}
def generate_permission_manifest(deployment: Deployment, agent_config: dict) -> dict:
"""Generate permission_manifest.json (ACL for enforcement)."""
return {
"user_id": deployment.user_id,
"binding_scope": deployment.binding_scope,
"agent_role": agent_config['role'],
"resource_grants": deployment.resource_grants
}
```
### 4.2 Model Gateway Token Router
**Files to create**:
```
services/model_gateway_router.py
```
**Implementation**:
```python
def get_model_gateway_env(deployment: Deployment) -> dict:
"""Get environment variables for model gateway based on provider."""
provider = deployment.billing_provider
if provider == "newapi":
# Phase 2-4: Use fallback token (from env)
# Phase 5: Fetch from Vault using secret_ref
token = os.getenv("HEICODE_NEWAPI_FALLBACK_TOKEN")
return {
"HEICODE_NEWAPI_BASE_URL": settings.HEICODE_NEWAPI_BASE_URL,
"HEICODE_NEWAPI_USER_TOKEN": token
}
elif provider == "litellm":
token = os.getenv("LITELLM_FALLBACK_TOKEN")
return {
"LITELLM_BASE_URL": settings.LITELLM_BASE_URL,
"LITELLM_USER_KEY": token
}
else:
raise ValueError(f"Invalid provider: {provider}")
```
### 4.3 K8s Deployment Creation
**Update `services/deployment_orchestrator.py`**:
```python
async def create_k8s_deployment(deployment: Deployment, agent_config: dict):
"""Create K8s resources for agent deployment."""
# 1. Create namespace
namespace = f"agnet-{hash_user_id(deployment.user_id)}"
k8s_manager.create_namespace_if_not_exists(namespace)
# 2. Create ServiceAccount
sa_name = f"sa-{agent_config['role']}-{hash_user_id(deployment.user_id)}"
k8s_manager.create_service_account(namespace, sa_name)
# 3. Generate ConfigMap content
agent_md = generate_agent_md(deployment, agent_config)
resource_context = generate_resource_context(deployment, agent_config)
permission_manifest = generate_permission_manifest(deployment, agent_config)
# 4. Create ConfigMap
configmap_name = f"{deployment.deployment_id}-config"
k8s_manager.create_configmap(
namespace,
configmap_name,
{
"AGENT.md": agent_md,
"resource_context.json": json.dumps(resource_context),
"permission_manifest.json": json.dumps(permission_manifest)
}
)
# 5. Get model gateway env vars
model_gateway_env = get_model_gateway_env(deployment)
# 6. Create Deployment
pod_env = {
"VAULT_ADDR": settings.VAULT_ADDR,
"VAULT_ROLE": sa_name,
**model_gateway_env
}
k8s_manager.create_deployment(
namespace=namespace,
name=f"agent-{deployment.deployment_id}",
image=agent_config['image'],
service_account=sa_name,
env_vars=pod_env,
volumes=[{
"name": "agent-config",
"configMap": {"name": configmap_name},
"mountPath": "/etc/agent/"
}],
resources={
"requests": {"cpu": "1000m", "memory": "2Gi"},
"limits": {"cpu": "4000m", "memory": "8Gi"}
}
)
return namespace, sa_name, configmap_name
```
**Acceptance criteria**:
- [ ] Namespace created with correct naming
- [ ] ServiceAccount created
- [ ] ConfigMap contains AGENT.md, resource_context.json, permission_manifest.json
- [ ] ConfigMap mounted to /etc/agent/ in pod
- [ ] Model gateway env vars injected based on provider
- [ ] NO long-term secrets in pod env
- [ ] Pod starts successfully
---
## Phase 5: Vault Integration & SK Snapshots (Days 23-30)
### 5.1 Vault Client
**Files to create**:
```
services/vault_client.py
```
**Implementation**:
```python
import hvac
class VaultClient:
def __init__(self):
self.client = hvac.Client(url=settings.VAULT_ADDR)
def get_secret(self, secret_ref: str) -> str:
"""Fetch secret from Vault using secret_ref.
Args:
secret_ref: Format "vault:secret/users/{user_id}/bindings/{scope}/..."
"""
# Parse secret_ref
path = secret_ref.replace("vault:", "")
# Authenticate using K8s service account token
with open("/var/run/secrets/kubernetes.io/serviceaccount/token") as f:
jwt = f.read()
self.client.auth.kubernetes.login(
role=settings.VAULT_ROLE,
jwt=jwt
)
# Read secret
secret = self.client.secrets.kv.v2.read_secret_version(path=path)
return secret['data']['data']['value']
vault_client = VaultClient()
```
### 5.2 Update Model Gateway Router
**Update `services/model_gateway_router.py`**:
```python
def get_model_gateway_env(deployment: Deployment) -> dict:
"""Get environment variables for model gateway based on provider."""
provider = deployment.billing_provider
# Phase 5: Fetch token from Vault
token = vault_client.get_secret(deployment.secret_ref)
if provider == "newapi":
return {
"HEICODE_NEWAPI_BASE_URL": settings.HEICODE_NEWAPI_BASE_URL,
"HEICODE_NEWAPI_USER_TOKEN": token
}
elif provider == "litellm":
return {
"LITELLM_BASE_URL": settings.LITELLM_BASE_URL,
"LITELLM_USER_KEY": token
}
```
### 5.3 SK Snapshot Endpoints
**Files to create**:
```
api/agnet/sk_snapshots.py
services/sk_snapshot_resolver.py
```
**Implementation**:
1. **POST /api/agnet/sk-snapshots/resolve**
- Parse sk_sources from deployment
- Clone git repos (read-only)
- Generate snapshot_id
- Store snapshot metadata
2. **GET /api/agnet/deployments/{id}/sk-snapshots**
- Query snapshot metadata
- Return list with status
**Acceptance criteria**:
- [ ] Vault client authenticates with K8s SA
- [ ] Model gateway tokens fetched from Vault
- [ ] SK snapshots resolved from git sources
- [ ] Snapshot metadata stored and queryable
---
## Phase 6: Testing & Hardening (Days 31-35)
### 6.1 Integration Tests
**Test suite**:
```
tests/
├── test_auth.py
├── test_deployments.py
├── test_observability.py
├── test_k8s_integration.py
├── test_vault_integration.py
└── test_backward_compat.py
```
### 6.2 Security Tests
1. Service token validation
2. Sensitive field rejection
3. Log redaction
4. Approval validation
5. Pod env isolation
### 6.3 Backward Compatibility Tests
1. GET /agents → 200
2. POST /agents → creates in old namespace
3. Old deployments unaffected
### 6.4 Performance Tests
1. Concurrent deployment creation (50 requests)
2. Log streaming performance
3. Metrics aggregation
**Acceptance criteria**:
- [ ] All integration tests passing
- [ ] Security tests passing
- [ ] Backward compatibility verified
- [ ] Performance benchmarks met
---
## Implementation Order
**Week 1 (Days 1-7)**:
- Phase 1: Foundation & Authentication (Days 1-3)
- Phase 2: Start Core Deployment Endpoints (Days 4-7)
**Week 2 (Days 8-14)**:
- Phase 2: Complete Core Deployment Endpoints (Days 8-10)
- Phase 3: Observability Endpoints (Days 11-14)
**Week 3 (Days 15-21)**:
- Phase 3: Complete Observability (Days 15-16)
- Phase 4: K8s Integration & Pod Startup (Days 16-21)
**Week 4 (Days 22-28)**:
- Phase 4: Complete K8s Integration (Days 22-23)
- Phase 5: Vault Integration & SK Snapshots (Days 23-28)
**Week 5 (Days 29-35)**:
- Phase 5: Complete Vault Integration (Days 29-30)
- Phase 6: Testing & Hardening (Days 31-35)
---
## Dependencies
**External**:
- mcp-server team: Service token format, test accounts
- Infra team: AKS Workload Identity, Vault deployment
- Heicode team: NewAPI endpoint, user token provisioning
**Internal**:
- Redis for idempotency cache
- PostgreSQL for new tables
- K8s cluster access
---
## Risk Mitigation
1. **Backward compatibility**: All new code isolated under `/api/agnet/*`
2. **Incremental rollout**: Phase-by-phase deployment with feature flags
3. **Fallback tokens**: Phase 2-4 use pre-shared tokens before Vault
4. **Testing**: Comprehensive test suite before production
---
## Success Criteria
- [ ] All 12 endpoints implemented
- [ ] Service token auth working
- [ ] Provider-based model gateway routing working
- [ ] Log redaction working
- [ ] Pod startup with ConfigMap working
- [ ] Vault integration working
- [ ] Backward compatibility maintained
- [ ] All tests passing
+623
View File
@@ -0,0 +1,623 @@
# code_ai_agent CI/CD 工作流方案设计
**计划文件:** `.omc/plans/code_ai_agent_cicd.md`
**创建日期:** 2026-03-27
**状态:** 待用户确认
---
## 1. 方案概述
将 `code_ai_agent` 从单纯的代码生成服务升级为具备完整 DevOps 工作流能力的「代码员工 Agent」。新增 Git 操作、SSH 远程执行、K8s 部署触发能力,全部通过 HTTP API 暴露。
### 完整工作流
```
外部调用方 (agent-manager / 人工)
│
▼
code_ai_agent Pod
┌──────────────────────────────────────────┐
│ api_server.py (HTTP 路由层) │
│ ┌──────────┬──────────┬──────────────┐ │
│ │ /git/* │ /ssh/* │ /deploy/k8s │ │
│ └────┬─────┴────┬─────┴──────┬───────┘ │
│ │ │ │ │
│ src/server/tools/ (工具实现层) │
│ ┌────▼─────┐ ┌──▼──────┐ ┌──▼────────┐ │
│ │git_tools │ │ssh_tools│ │deploy_tools│ │
│ └────┬─────┘ └──┬──────┘ └──┬────────┘ │
│ │ │ │ │
│ /workspace/{task_id}/ (隔离工作空间) │
└───┬───┴──────────┴────────────┴───────────┘
│
├─► Gitee (http://gitee.ath.cx:3000)
├─► Azure VM (SSH 22)
└─► K8s API Server
```
### 典型工作流序列
```
1. POST /api/v1/git/clone → 克隆仓库到 /workspace/{task_id}
2. POST /api/v1/git/branch → 创建 feature/xxx 分支
3. POST /api/v1/code/generate → 使用现有能力生成/修改代码
4. POST /api/v1/git/status → 确认变更
5. POST /api/v1/git/commit-push → 提交并推送
6. POST /api/v1/ssh/exec → SSH 到 Azure VM 执行测试
7. POST /api/v1/deploy/k8s → 测试通过后触发 K8s 部署
```
---
## 2. 新增 API 端点设计(api_server.py)
### 2.1 Git 操作端点
#### `POST /api/v1/git/clone`
```json
// 请求
{
"repo_url": "http://gitee.ath.cx:3000/zhanggangyong/agent_management.git",
"task_id": "task-20260327-001",
"branch": "main",
"depth": 1
}
// 响应
{
"success": true,
"task_id": "task-20260327-001",
"workspace": "/workspace/task-20260327-001",
"branch": "main",
"commit": "abc1234"
}
```
#### `POST /api/v1/git/branch`
```json
// 请求
{
"task_id": "task-20260327-001",
"branch_name": "feature/auto-fix-bug-123",
"from_branch": "main"
}
// 响应
{ "success": true, "branch": "feature/auto-fix-bug-123", "base_commit": "abc1234" }
```
#### `POST /api/v1/git/status`
```json
// 请求
{ "task_id": "task-20260327-001" }
// 响应
{
"success": true,
"branch": "feature/auto-fix-bug-123",
"staged": ["src/main.py"],
"unstaged": ["README.md"],
"untracked": ["new_file.py"],
"raw_output": "M src/main.py\n?? new_file.py"
}
```
#### `POST /api/v1/git/commit-push`
```json
// 请求
{
"task_id": "task-20260327-001",
"message": "fix: resolve null pointer in agent executor",
"files": ["src/agent.py"],
"push": true
}
// 响应
{ "success": true, "commit": "def5678", "pushed": true, "branch": "feature/auto-fix-bug-123" }
```
#### `POST /api/v1/git/diff`
```json
// 请求
{ "task_id": "task-20260327-001", "staged": false }
// 响应
{ "success": true, "diff": "--- a/src/main.py\n+++ b/src/main.py\n..." }
```
### 2.2 SSH 操作端点
#### `POST /api/v1/ssh/exec`
```json
// 请求
{
"host": "<azure-vm-ip>",
"user": "azureuser",
"command": "cd /app && pytest tests/ -v --tb=short",
"timeout": 300,
"task_id": "task-20260327-001"
}
// 响应
{
"success": true,
"exit_code": 0,
"stdout": "collected 42 items ... 42 passed",
"stderr": "",
"duration_seconds": 45.2
}
```
**说明:** `host` 若不传,从环境变量 `SSH_TEST_HOST` 读取;`user` 从 `SSH_USER` 读取,默认 `azureuser`。
### 2.3 部署端点
#### `POST /api/v1/deploy/k8s`
```json
// 请求
{
"namespace": "agent-manager",
"deployment": "agent-manager",
"image": "agnettaiji.azurecr.io/ai-agents/agent-manager:v1.2.3",
"strategy": "set-image",
"wait": true,
"timeout": 300
}
// strategy: "rollout-restart" | "set-image"
// 响应
{ "success": true, "deployment": "agent-manager", "status": "rolled out", "duration_seconds": 62 }
```
---
## 3. 新增工具函数设计(mcp_server.py + tools/ 模块)
### 3.1 文件结构变化
```
agent_templates/agents/code_ai_agent/
├── Dockerfile # 修改:增加 git/ssh/kubectl
├── requirements.txt # 修改:增加 paramiko, gitpython
├── src/server/
│ ├── api_server.py # 修改:新增 /git /ssh /deploy 路由
│ ├── mcp_server.py # 修改:新增工具注册
│ ├── mcp_http_server.py # 不变
│ └── tools/ # 新增目录
│ ├── __init__.py
│ ├── git_tools.py # Git 操作实现
│ ├── ssh_tools.py # SSH 操作实现
│ ├── deploy_tools.py # K8s 部署实现
│ └── workspace.py # 工作空间管理
└── k8s/ # 新增:agent 专属 K8s 配置
├── code-ai-agent-deployment.yaml
└── code-ai-agent-secret.yaml
```
### 3.2 git_tools.py 核心接口
```python
class GitTools:
def __init__(self):
self.workspace_root = "/workspace"
self._gitee_user = os.getenv("GITEE_USERNAME")
self._gitee_token = os.getenv("GITEE_TOKEN")
def clone(self, repo_url, task_id, branch="main", depth=1) -> dict
def create_branch(self, task_id, branch_name, from_branch=None) -> dict
def get_status(self, task_id) -> dict
def stage_files(self, task_id, files=None) -> dict # None = git add -A
def commit(self, task_id, message) -> dict
def push(self, task_id, branch=None) -> dict
def get_diff(self, task_id, staged=False) -> dict
def cleanup(self, task_id) -> dict # 删除工作空间
def _inject_credentials(self, repo_url) -> str:
# http://user:token@gitee.ath.cx:3000/...
parsed = urlparse(repo_url)
return parsed._replace(
netloc=f"{self._gitee_user}:{self._gitee_token}@{parsed.hostname}:{parsed.port}"
).geturl()
def _run(self, cmd, cwd) -> tuple[int, str, str]
# subprocess.run,捕获 stdout/stderr,设置超时
```
### 3.3 ssh_tools.py 核心接口
```python
class SSHTools:
def __init__(self):
self._key_path = "/root/.ssh/id_rsa" # 从 Secret 挂载
self._default_host = os.getenv("SSH_TEST_HOST")
self._default_user = os.getenv("SSH_USER", "azureuser")
def exec(self, command, host=None, user=None, timeout=120, task_id=None) -> dict:
# 使用 paramiko 连接,执行命令,返回 stdout/stderr/exit_code
# 每次调用建立新连接,操作完毕后关闭
def _get_client(self, host, user) -> paramiko.SSHClient
```
### 3.4 deploy_tools.py 核心接口
```python
class DeployTools:
def __init__(self):
# 优先使用挂载的 kubeconfig,其次 in-cluster config
self._kubeconfig = "/root/.kube/config"
def rollout_restart(self, namespace, deployment, wait=True, timeout=300) -> dict
def set_image(self, namespace, deployment, image, wait=True, timeout=300) -> dict
def get_status(self, namespace, deployment) -> dict
def _run_kubectl(self, args) -> tuple[int, str, str]
```
### 3.5 workspace.py — 工作空间管理
```python
class WorkspaceManager:
ROOT = "/workspace"
@staticmethod
def get_path(task_id: str) -> str:
# 返回 /workspace/{task_id}
# task_id 只允许 [a-zA-Z0-9_-],防止路径注入
@staticmethod
def create(task_id: str) -> str
@staticmethod
def cleanup(task_id: str) -> None
@staticmethod
def list_tasks() -> list[str]
@staticmethod
def disk_usage() -> dict # 返回各 task_id 占用磁盘大小
```
---
## 4. 安全设计
### 4.1 SSH 私钥注入
**方案:K8s Secret → Volume Mount(只读)**
```yaml
# 新建 Secret(在 code-ai-agent 命名空间下)
apiVersion: v1
kind: Secret
metadata:
name: code-ai-agent-ssh-secret
namespace: agent-manager
type: Opaque
data:
id_rsa: <base64-encoded-private-key>
id_rsa.pub: <base64-encoded-public-key>
known_hosts: <base64-encoded-known_hosts> # 预置 Azure VM
```
```yaml
# Deployment volumeMounts
volumeMounts:
- name: ssh-secret
mountPath: /root/.ssh
readOnly: true
volumes:
- name: ssh-secret
secret:
secretName: code-ai-agent-ssh-secret
defaultMode: 0400 # 私钥必须 0400,否则 SSH 拒绝
```
初始化:容器 entrypoint 或 initContainer 执行 `chmod 700 /root/.ssh && chmod 600 /root/.ssh/id_rsa`。
### 4.2 Git 凭证安全传递
| 方案 | 说明 | 推荐度 |
|------|------|--------|
| Token 嵌入 URL | `http://user:token@host/repo` 内存拼接,不落盘 | P0 首选 |
| git credential store | 写入 `~/.git-credentials` 文件权限 600 | 备选 |
| SSH key for git | gitee 配置 deploy key,统一 SSH | P2 升级 |
实现要点:`_inject_credentials()` 在内存拼接带 token 的 URL;clone 完成后用 `git remote set-url origin <无密码URL>` 替换;日志中对 URL 做 token 脱敏。
### 4.3 权限隔离
- code_ai_agent 使用独立 ServiceAccount `code-ai-agent`
- RBAC 只授予 `agent-manager` 命名空间下 Deployment 的 `get/patch/update`
- SSH 连接只允许白名单 host(`SSH_ALLOWED_HOSTS` 环境变量,ssh_tools.py 校验)
- `/workspace` 挂载独立 emptyDir,不与其他 agent 共享
- API 通过现有 `X-API-Key` header 鉴权
---
## 5. 工作空间设计
### 5.1 目录结构
```
/workspace/
├── task-20260327-001/
│ ├── agent_management/ # 克隆的仓库
│ └── .meta.json # 任务元数据(时间、branch、状态)
├── task-20260327-002/
│ └── agent_management/
└── .workspace_index.json
```
### 5.2 并发隔离策略
- `task_id` 由调用方传入或服务端 `uuid4()` 自动生成
- 每个 task_id 对应独立目录,无共享文件
- 任务完成后调用清理接口或设置 TTL 自动清理
- 磁盘告警:workspace 总占用超过 10GB 返回 503
- `task_id` 只允许 `[a-zA-Z0-9_-]`,防止路径穿越注入
### 5.3 新增管理端点
```
GET /api/v1/workspace/list → 列出所有 task_id 和磁盘占用
DELETE /api/v1/workspace/{task_id} → 清理指定工作空间
```
---
## 6. Dockerfile 修改
**当前状态:** 只安装 `gcc`,无 git/ssh/kubectl。
**修改后关键变更:**
```dockerfile
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
# 新增:git + openssh-client + curl(kubectl 安装需要)
RUN apt-get update && apt-get install -y \
gcc git openssh-client curl ca-certificates gnupg \
&& rm -rf /var/lib/apt/lists/*
# 新增:安装 kubectl
RUN curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
&& chmod +x kubectl && mv kubectl /usr/local/bin/
# 新增:paramiko(SSH)、gitpython(可选,subprocess git 为主)
RUN pip install --no-cache-dir -r requirements.txt requests paramiko gitpython
# 新增:工作空间目录(PVC 挂载时会覆盖)
RUN mkdir -p /workspace /tmp/projects
EXPOSE 8000 8001
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["python", "run_api_server.py"]
```
**镜像大小预估影响:** git + openssh ≈ +30MB,kubectl ≈ +50MB,paramiko ≈ +5MB。总增量约 85MB,可接受。
---
## 7. K8s 部署配置修改
### 7.1 新增文件:code-ai-agent-deployment.yaml
code_ai_agent 需要独立 Deployment(与 agent-manager 主服务分离),关键新增配置段:
```yaml
spec:
template:
spec:
serviceAccountName: code-ai-agent
containers:
- name: code-ai-agent
env:
- name: GITEE_USERNAME
valueFrom:
secretKeyRef:
name: agent-manager-secret
key: GITEE_USERNAME
- name: GITEE_TOKEN
valueFrom:
secretKeyRef:
name: agent-manager-secret
key: GITEE_TOKEN
- name: SSH_TEST_HOST
valueFrom:
secretKeyRef:
name: code-ai-agent-ssh-secret
key: SSH_TEST_HOST
- name: SSH_USER
value: "azureuser"
volumeMounts:
- name: ssh-secret
mountPath: /root/.ssh
readOnly: true
- name: kubeconfig
mountPath: /root/.kube
readOnly: true
- name: workspace
mountPath: /workspace
resources:
requests:
memory: "512Mi"
cpu: "300m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: ssh-secret
secret:
secretName: code-ai-agent-ssh-secret
defaultMode: 0400
- name: kubeconfig
secret:
secretName: kubeconfig-secret
optional: true
- name: workspace
emptyDir:
sizeLimit: 20Gi
```
### 7.2 agent-manager-secret 新增 key
在现有 `k8s/agent-manager-secret.yaml` 补充:
```yaml
GITEE_USERNAME: "zhanggangyong"
```
### 7.3 新建 code-ai-agent-ssh-secret.yaml
```yaml
apiVersion: v1
kind: Secret
metadata:
name: code-ai-agent-ssh-secret
namespace: agent-manager
type: Opaque
data:
id_rsa: <base64-encoded-private-key>
known_hosts: <base64-encoded-known_hosts>
SSH_TEST_HOST: <base64-encoded-azure-vm-ip>
```
### 7.4 RBAC 新增 Role + RoleBinding
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: code-ai-agent-role
namespace: agent-manager
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
```
---
## 8. 实现优先级
### P0 — 核心能力(第一阶段,必须先完成)
| 编号 | 内容 | 验收标准 |
|------|------|----------|
| P0-1 | Dockerfile 安装 git + openssh-client + kubectl | `docker run ... git --version` 输出正常 |
| P0-2 | workspace.py 工作空间管理 | 单元测试覆盖路径注入防护(task_id 含 `../` 时拒绝)|
| P0-3 | git_tools.py:clone + branch + status + commit + push | 成功 clone gitee 仓库,创建分支并推送 |
| P0-4 | ssh_tools.py:exec | SSH 到 Azure VM 执行 `echo ok`,返回 exit_code=0 |
| P0-5 | api_server.py 新增 /git/* 和 /ssh/exec 路由 | HTTP 调用返回正确 JSON,异常时返回 4xx/5xx |
| P0-6 | SSH Secret + Volume Mount K8s 配置 | Pod 启动后 `/root/.ssh/id_rsa` 权限为 0400 |
### P1 — 完整工作流(第二阶段)
| 编号 | 内容 | 验收标准 |
|------|------|----------|
| P1-1 | deploy_tools.py:rollout-restart + set-image | 成功触发 K8s 滚动更新,等待就绪返回 |
| P1-2 | api_server.py 新增 /deploy/k8s 路由 | 调用后 deployment 完成更新,status 字段正确 |
| P1-3 | RBAC:code-ai-agent ServiceAccount + Role | `kubectl auth can-i patch deployment` 返回 yes |
| P1-4 | git diff 接口 | 返回正确 unified diff 格式 |
| P1-5 | workspace list/cleanup 管理端点 | GET /workspace/list 返回含磁盘占用的列表 |
| P1-6 | mcp_server.py 注册新工具 | MCP 工具列表中出现 git_clone、ssh_exec、k8s_deploy |
### P2 — 增强与优化(第三阶段)
| 编号 | 内容 | 说明 |
|------|------|------|
| P2-1 | 替换 HTTP token 为 SSH key 方式访问 git | 更安全,需 gitee 配置 deploy key |
| P2-2 | workspace 磁盘告警 + TTL 自动清理 | 防止 emptyDir 耗尽,定时任务每小时扫描 |
| P2-3 | SSH 连接池(paramiko Transport 复用) | 减少高频调用连接建立开销 |
| P2-4 | /api/v1/pipeline/run 编排端点 | 单次调用完成 clone→修改→测试→部署全流程 |
| P2-5 | 操作审计日志(structured log) | 所有 git/ssh/deploy 操作可追溯,含 task_id |
---
## 9. 潜在风险与注意事项
### 风险 1:Git Token 泄露
- **场景:** token 嵌入 URL 后被 `git remote -v`、进程环境变量或日志打印
- **缓解:** clone 后立即 `git remote set-url origin <无密码URL>`;日志中 URL 做正则脱敏;不将 token 写入任何文件
### 风险 2:workspace 磁盘耗尽
- **场景:** 大量任务未清理,emptyDir 超限导致 Pod 被驱逐
- **缓解:** emptyDir 设 `sizeLimit: 20Gi`;API 层磁盘检查(超 10GB 返回 503);P2 阶段加 TTL 自动清理
### 风险 3:SSH 私钥被容器内进程读取
- **场景:** 容器内其他进程或代码执行漏洞读取 `/root/.ssh/id_rsa`
- **缓解:** Volume `defaultMode: 0400`;容器以非 root 用户运行(P2 阶段);考虑使用 Vault Agent Injector 替代 Secret Volume
### 风险 4:K8s 部署权限过宽
- **场景:** code_ai_agent 被攻击后可滥用 kubectl 权限影响其他服务
- **缓解:** RBAC 严格限制到 `agent-manager` 命名空间,只允许 get/patch/update Deployment;禁止 delete、exec、secret 等危险操作
### 风险 5:并发 git 操作冲突
- **场景:** 两个任务使用相同 task_id 或同一仓库并发操作
- **缓解:** task_id 全局唯一(UUID);每个 task_id 独立目录;api_server.py 对同一 task_id 的写操作加文件锁
### 风险 6:Azure VM SSH 连接超时或不可达
- **场景:** 网络抖动或 VM 重启导致 SSH 命令挂起
- **缓解:** paramiko 设置 `banner_timeout`、`auth_timeout`、`timeout`;所有 ssh.exec 调用强制设置 `timeout` 参数(默认 120s);超时后返回明确错误而非挂起
### 风险 7:CI/CD 循环触发
- **场景:** code_ai_agent 推送代码触发 CI,CI 再触发 code_ai_agent,形成死循环
- **缓解:** commit message 加 `[skip-ci]` 标记;部署端点需要明确的 image tag 参数,不自动推断
---
## 10. 工作计划(Task Flow)
### Step 1:基础设施准备(P0-1, P0-6)
- 修改 `Dockerfile`,安装 git/openssh/kubectl
- 创建 `code-ai-agent-ssh-secret.yaml`
- 更新 `agent-manager-secret.yaml` 补充 `GITEE_USERNAME`
- **验收:** Pod 启动正常,`/root/.ssh/id_rsa` 权限 0400
### Step 2:工作空间与 Git 工具(P0-2, P0-3)
- 实现 `src/server/tools/workspace.py`
- 实现 `src/server/tools/git_tools.py`
- 编写单元测试
- **验收:** 能 clone gitee 仓库,创建分支,commit+push
### Step 3:SSH 工具与 API 路由(P0-4, P0-5)
- 实现 `src/server/tools/ssh_tools.py`
- 在 `api_server.py` 注册 `/git/*` 和 `/ssh/exec` 路由
- **验收:** HTTP 调用 clone + ssh exec 全流程通
### Step 4:部署工具与完整流程(P1-1 ~ P1-3)
- 实现 `src/server/tools/deploy_tools.py`
- 注册 `/deploy/k8s` 路由
- 配置 RBAC
- **验收:** 调用 `/deploy/k8s` 触发滚动更新成功
### Step 5:MCP 工具注册与增强(P1-4 ~ P1-6, P2)
- 在 `mcp_server.py` 注册新工具
- workspace 管理端点
- 按需推进 P2 优化项
---
## 成功标准
1. 完整工作流(clone → branch → 代码修改 → commit/push → SSH 测试 → K8s 部署)可通过 HTTP API 驱动,无人工干预
2. 所有凭证(git token、SSH 私钥)通过 K8s Secret 注入,不硬编码
3. 并发多任务互不干扰(task_id 隔离)
4. 单个操作失败有明确错误信息,不影响其他任务
5. Pod 重启后工作空间可按需重建(无状态设计)
---
**Does this plan capture your intent?**
- `proceed` — 开始实现,移交 executor
- `adjust [X]` — 返回调整某个模块设计
- `restart` — 废弃重新开始
+310
View File
@@ -0,0 +1,310 @@
# Heicode Integration Development Plan
**Based on**: Agent-Manager-Heicode对接需求文档(2).md v1.1
**Target**: Implement 12 new `/api/agnet/*` endpoints + Pod startup changes
**Timeline**: 3-4 weeks (5 phases)
---
## Phase 1: Foundation & Authentication (2-3 days)
### 1.1 Service Token Authentication
- [ ] Add service token validation middleware
- [ ] Support `Authorization: Bearer <token>` header validation
- [ ] Implement token verification (start with pre-shared token, option A)
- [ ] Add correlation/request ID tracking (`X-Correlation-Id`, `X-User-Id`, `X-Binding-Scope`)
- [ ] Add `Idempotency-Key` support with caching mechanism
### 1.2 Error Response Structure
- [ ] Implement standardized error response format:
```json
{
"success": false,
"error": {
"code": "POLICY_REJECTED",
"message": "human readable",
"request_id": "req_xxx"
}
}
```
- [ ] Define error code constants (POLICY_REJECTED, BUDGET_EXCEEDED, MODEL_NOT_ALLOWED, etc.)
- [ ] Add error code mapping and response helpers
### 1.3 Project Structure
- [ ] Create `/api/agnet` router module
- [ ] Set up request/response models (Pydantic schemas)
- [ ] Add logging infrastructure with correlation ID support
- [ ] Set up configuration for new endpoints (separate from existing `/agents/*`)
**Deliverable**: Service token auth working, error responses standardized
---
## Phase 2: Core Deployment Endpoints (5-7 days)
### 2.1 POST /api/agnet/deployments (Create)
- [ ] Implement request payload validation:
- Required fields: `orchestration_plan`, `agents[]`, `risk_level`, `budget`, `metadata.correlation_id`
- Validate `billing_context.provider` enum (`newapi` | `litellm`)
- Validate `resource_grants[]` structure
- Validate `default_model_id` ∈ `allowed_model_ids`
- [ ] Implement sensitive field rejection (recursive scan for password/token/secret/private_key/access_key)
- [ ] Implement approval validation for `risk_level=high`
- [ ] Add idempotency check (return existing result if same key)
- [ ] Return deployment response with `deployment_id`, `status`, `agent_instances[]`
### 2.2 GET /api/agnet/deployments (List)
- [ ] Implement pagination with cursor support
- [ ] Filter by `user_id`, `binding_scope`, `status`
- [ ] Return deployment list with basic info
### 2.3 GET /api/agnet/deployments/{id} (Details)
- [ ] Return full deployment details
- [ ] Include agent instances with current phase
- [ ] Include resource grants summary
### 2.4 POST /api/agnet/deployments/{id}/stop (Stop)
- [ ] Implement idempotent stop logic
- [ ] Handle already-stopped deployments (200 + status=stopped)
- [ ] Handle terminal state conflicts (409 DEPLOYMENT_CONFLICT)
- [ ] Validate approval for high-risk stops
**Deliverable**: Core CRUD endpoints working with mock K8s backend
---
## Phase 3: Observability Endpoints (3-5 days)
### 3.1 GET /api/agnet/deployments/{id}/logs
- [ ] Implement log retrieval from K8s pods
- [ ] **Mandatory log redaction**: scan and mask passwords/tokens/keys/connection strings
- [ ] Support query params: `agent_instance_id`, `stream`, `since`, `limit`, `cursor`
- [ ] Return structured log entries with `log_id`, `stream`, `level`, `message`, `redacted`, `occurred_at`
### 3.2 GET /api/agnet/deployments/{id}/logs/stream (Optional SSE)
- [ ] Implement SSE streaming for real-time logs
- [ ] Apply same redaction rules as batch logs
- [ ] Handle client disconnection gracefully
### 3.3 GET /api/agnet/deployments/{id}/events
- [ ] Implement event storage/retrieval
- [ ] Support event types: `deployment.accepted`, `instance.phase_changed`, `sk_snapshot_refreshed`, `resource_grant.attached/revoked`, `budget.threshold_reached`, `deployment.failed`
- [ ] Support filtering by event type, time range
- [ ] Return structured events with `event_id`, `event`, `correlation_id`, `occurred_at`
### 3.4 GET /api/agnet/deployments/{id}/metrics
- [ ] Implement time-series metrics retrieval
- [ ] Support metrics: `tokens_used`, `cost_usd`, `duration_sec`, `cpu_millicores`, `memory_mb`, `restart_count`, `tool_call_count`, `error_count`, `queue_latency_ms`
- [ ] Support `window` and `step` parameters
### 3.5 GET /api/agnet/projects/{binding_scope}/dashboard-snapshot
- [ ] Aggregate metrics across deployments in binding_scope
- [ ] Return: `active_instances`, `phase_distribution`, `failure_rate_1h`, `avg_task_duration`, `budget`, `resource_usage`, `updated_at`
### 3.6 GET /api/agnet/audit-logs
- [ ] Implement audit log storage/retrieval
- [ ] Support filtering by `user_id`, `binding_scope`, `actor`, `action`, `since`
- [ ] Return structured audit entries with `audit_id`, `actor`, `action`, `resource`, `result`, `occurred_at`
**Deliverable**: All observability endpoints working with real K8s data
---
## Phase 4: K8s Integration & Pod Startup (5-7 days)
### 4.1 K8s Deployment Creation
- [ ] Implement K8s client integration
- [ ] Create namespace strategy: `agnet-{user_id_hash}` (separate from old namespaces)
- [ ] Create ServiceAccount per deployment: `sa-{role}-{user_id_hash}`
- [ ] Bind SA to Vault Kubernetes Auth role
### 4.2 ConfigMap Generation
- [ ] Generate `AGENT.md` from deployment payload (natural language context)
- [ ] Generate `resource_context.json` (structured metadata, NO secrets)
- [ ] Generate `permission_manifest.json` (structured permissions for enforcement)
- [ ] Create ConfigMap and mount to Pod at `/etc/agent/`
### 4.3 Model Gateway Token Routing (v1.1 Critical)
- [ ] Implement provider-based token routing:
- `provider=newapi`:
- Fetch token from `secret_ref` (Vault or fallback)
- Inject env: `HEICODE_NEWAPI_BASE_URL=https://code.xinghanlab.com`
- Inject env: `HEICODE_NEWAPI_USER_TOKEN=<token>`
- `provider=litellm`:
- Fetch token from `secret_ref` (Vault or fallback)
- Inject env: `LITELLM_BASE_URL=<internal_litellm_url>`
- Inject env: `LITELLM_USER_KEY=<token>`
- [ ] Add fallback for Phase 2-3 testing (pre-shared token with annotation)
- [ ] Annotate deployment with `heicode.io/token-source` and `secret_ref` for audit
### 4.4 Pod Environment Setup
- [ ] Inject Vault env vars: `VAULT_ADDR`, `VAULT_AUTH_PATH`, `VAULT_ROLE`
- [ ] Inject model gateway env vars (based on provider)
- [ ] **NO long-term secrets in env** (enforce in code review)
- [ ] Mount ConfigMap volumes
### 4.5 Deployment Spec
- [ ] Create Deployment with:
- `serviceAccountName`: SA created in 4.1
- `volumeMounts`: ConfigMap from 4.2
- `env`: Vault + model gateway vars from 4.3-4.4
- Container image, resource limits, health checks
- [ ] Track deployment status and update internal state
**Deliverable**: Real K8s pods launching with correct configuration
---
## Phase 5: Vault Integration & SK Snapshots (1-2 weeks)
### 5.1 AKS Workload Identity Setup (with infra team)
- [ ] Enable OIDC issuer + Workload Identity addon on AKS
- [ ] Configure ServiceAccount annotations: `azure.workload.identity/client-id`
- [ ] Set up Federated Identity Credential in Azure AD
### 5.2 Vault Kubernetes Auth
- [ ] Configure Vault policies per `(user_id, binding_scope)`:
```hcl
path "secret/users/${user_id}/bindings/${binding_scope}/resources/*" {
capabilities = ["read"]
}
```
- [ ] Configure Vault Kubernetes Auth roles binding SA → policy
- [ ] Test Pod → Vault authentication flow
### 5.3 Secret Retrieval
- [ ] Implement Vault client in agent-manager
- [ ] Fetch model gateway tokens from Vault using `secret_ref`
- [ ] Remove fallback pre-shared token path (Phase 2-3 temporary)
- [ ] Add token TTL tracking and refresh logic
### 5.4 SK Snapshot Endpoints
- [ ] POST /api/agnet/sk-snapshots/resolve:
- Parse `agents[].sk_sources[]` (git/upload resources)
- Fetch resources and generate read-only snapshot
- Generate `snapshot_id`, `artifact_ref`, `checksum`
- Store snapshot metadata
- [ ] GET /api/agnet/deployments/{id}/sk-snapshots:
- Return snapshots list with `source_ref`, `resolved_at`, `status`
**Deliverable**: Full Vault integration, SK snapshots working
---
## Phase 6: Testing & Hardening (1 week)
### 6.1 Security Testing
- [ ] Test service token validation (401 on invalid token)
- [ ] Test sensitive field rejection (422 on plaintext secrets)
- [ ] Test log redaction (no secrets in log output)
- [ ] Test approval validation for high-risk operations
- [ ] Test Pod env isolation (no long-term secrets)
### 6.2 Integration Testing
- [ ] Test full deployment flow: create → running → logs → metrics → stop
- [ ] Test both `provider=newapi` and `provider=litellm` paths
- [ ] Test idempotency (same Idempotency-Key returns same result)
- [ ] Test error handling (all error codes)
- [ ] Test pagination and filtering
### 6.3 Backward Compatibility Testing
- [ ] Verify existing `/agents/*` endpoints still work
- [ ] Verify old taiji deployments unaffected
- [ ] Verify namespace isolation (old vs new)
### 6.4 Performance Testing
- [ ] Test concurrent deployment creation
- [ ] Test log streaming performance
- [ ] Test metrics aggregation performance
**Deliverable**: Production-ready implementation
---
## Cross-Cutting Concerns
### Documentation
- [ ] API documentation (OpenAPI/Swagger)
- [ ] Deployment guide for ops team
- [ ] Security review checklist
- [ ] Runbook for common issues
### Monitoring
- [ ] Add metrics for new endpoints (latency, error rate)
- [ ] Add alerts for deployment failures
- [ ] Add audit logging for all operations
### Configuration
- [ ] Environment variables for Vault, K8s, model gateways
- [ ] Feature flags for gradual rollout
- [ ] Configuration validation on startup
---
## Dependencies & Blockers
### External Dependencies
- **mcp-server team**: Service token format, test accounts, APIM routing
- **Infra team**: AKS Workload Identity setup, Vault deployment, network policies
- **Heicode team**: NewAPI endpoint, user token provisioning
### Decision Points
- [ ] Service token scheme: A (pre-shared) vs B (JWT) vs C (Workload Identity)
- **Recommendation**: Start with A, migrate to C in Phase 5
- [ ] Staging environment base URL for mcp-server
- [ ] Model gateway fallback token limits ($1/day for testing)
---
## Rollout Strategy
### Phase 2-3: Mock Backend
- New endpoints return mock data
- No real K8s operations
- Focus on contract validation
### Phase 4: Staging K8s
- Real K8s deployments in staging cluster
- Pre-shared tokens for model gateways
- Limited user testing
### Phase 5: Production
- Vault integration complete
- Full security hardening
- Gradual rollout with feature flags
---
## Success Criteria
- [ ] All 12 endpoints implemented and tested
- [ ] Pod startup follows security requirements (no long-term secrets)
- [ ] Both `provider=newapi` and `provider=litellm` paths working
- [ ] Log redaction working (no secrets leaked)
- [ ] Backward compatibility maintained (old endpoints unchanged)
- [ ] Integration tests passing with mcp-server
- [ ] Security review approved
- [ ] Production deployment successful
---
## Timeline Summary
| Phase | Duration | Key Deliverable |
|-------|----------|-----------------|
| Phase 1 | 2-3 days | Auth & error handling |
| Phase 2 | 5-7 days | Core CRUD endpoints |
| Phase 3 | 3-5 days | Observability endpoints |
| Phase 4 | 5-7 days | K8s integration |
| Phase 5 | 1-2 weeks | Vault + SK snapshots |
| Phase 6 | 1 week | Testing & hardening |
| **Total** | **3-4 weeks** | Production-ready |
---
## Next Steps
1. Review plan with team
2. Confirm service token scheme with mcp-server team
3. Set up staging environment
4. Start Phase 1 implementation
+7
View File
@@ -0,0 +1,7 @@
## code_ai_agent_cicd - 2026-03-27
- [ ] Azure VM 的 IP 地址和 SSH 用户名是什么? — 需要填入 code-ai-agent-ssh-secret 的 SSH_TEST_HOST 字段
- [ ] SSH 私钥是否已存在?还是需要新生成并将公钥部署到 Azure VM? — 影响 Secret 创建流程
- [ ] code_ai_agent 是否有专属 Deployment?还是目前通过 agent-manager 动态启动? — 决定是新建 Deployment 还是修改现有配置
- [ ] 测试命令是什么(Azure VM 上执行)?例如 `pytest tests/` 还是其他脚本? — 影响 SSH exec 的默认命令设计
- [ ] K8s 部署触发后,image tag 如何确定?是调用方传入还是从 CI 环境变量读取? — 影响 /deploy/k8s 接口设计
- [ ] GITEE_USERNAME 是否已在 agent-manager-secret 中?当前 secret.yaml 中未见此 key — 需确认后补充