Author SHA1 Message Date
zhanggangyong 090f4a04b3 fix: 更新 Azure Service Principal 凭据
- 更新 AZ_CLIENT_ID 为新的 SP
- 更新 AZ_CLIENT_SECRET
- 更新 AZ_SUBSCRIPTION_ID 为新订阅
2026-02-05 07:31:02 +00:00
9 changed files with 649 additions and 1733 deletions
-5
View File
@@ -16,13 +16,8 @@ COPY template_manager.py .
COPY gitee_manager.py .
COPY agent_code_generator.py .
COPY tool_generator_api.py .
COPY external_tool_api.py .
COPY tool_storage.py .
COPY agent_manager/ ./agent_manager/
# 创建工具存储目录
RUN mkdir -p /app/tool_storage
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
+137 -1140
View File
File diff suppressed because it is too large Load Diff
-41
View File
@@ -16,8 +16,6 @@ from database import (
)
from template_manager import template_manager
from tool_generator_api import router as tool_generator_router
from external_tool_api import router as external_tool_router
from tool_storage import tool_storage
import os
# 配置日志
@@ -34,9 +32,6 @@ app = FastAPI(
# 注册动态工具生成 Router
app.include_router(tool_generator_router)
# 注册外部工具 API Router(符合 MCP-Server 规范)
app.include_router(external_tool_router)
# 初始化K8s管理器
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
KUBECONFIG_PATH = os.getenv("KUBECONFIG_PATH", None) # 可选:指定kubeconfig路径
@@ -212,7 +207,6 @@ class CreateAgentRequest(BaseModel):
config: Dict = Field(default_factory=dict, description="配置信息")
env: Optional[Dict[str, str]] = Field(default_factory=dict, description="环境变量")
namespace: Optional[str] = Field(default=None, description="Kubernetes命名空间,默认使用环境变量NAMESPACE的值")
tool_refs: Optional[List[str]] = Field(default=None, description="外部数据工具标识列表(符合MCP-Server规范)")
class AgentResponse(BaseModel):
@@ -230,7 +224,6 @@ class AgentResponse(BaseModel):
host_ip: Optional[str] = None
node_name: Optional[str] = None
owner_info: Optional[Dict] = None
tools_attached: Optional[int] = Field(default=0, description="附加的外部工具数量")
class ResourceUsage(BaseModel):
@@ -335,36 +328,6 @@ async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db
config_data["env"] = request.env
logger.info(f"环境变量: {list(request.env.keys())}")
# 处理 tool_refs(符合 MCP-Server 规范)
attached_tools = []
if request.tool_refs:
logger.info(f"📦 处理外部工具引用: {request.tool_refs}")
# 验证所有工具存在
missing_tools = []
for ref in request.tool_refs:
tool = tool_storage.get_tool(ref)
if tool:
attached_tools.append(tool)
else:
missing_tools.append(ref)
if missing_tools:
raise HTTPException(
status_code=404,
detail=f"以下工具不存在: {', '.join(missing_tools)}"
)
# 将工具配置添加到 config
config_data["tool_refs"] = request.tool_refs
config_data["tools_count"] = len(attached_tools)
# 标记工具被使用
for ref in request.tool_refs:
tool_storage.mark_tool_in_use(ref, request.name)
logger.info(f"✅ 已附加 {len(attached_tools)} 个外部工具")
# 添加 user_id 标签
user_id = config_data.get("user_id", "default")
if "labels" not in config_data:
@@ -553,10 +516,6 @@ async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db
if "status" not in result:
result["status"] = "Pending"
# 添加外部工具信息
if attached_tools:
result["tools_attached"] = len(attached_tools)
try:
return AgentResponse(**result)
except Exception as validation_error:
+422 -205
View File
@@ -1,7 +1,7 @@
# 动态 Agent 生成器 API 文档
> **版本**: 2026-01-29 v1.1
> **服务地址**: http://20.212.121.126
> **版本**: 2026-01-26 v1.0
> **服务地址**: http://57.158.136.164
> **模板参考**: [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS)
---
@@ -29,12 +29,6 @@
│ │ AKS │ │
│ │ (部署) │ │
│ └──────────┘ │
│ │ │
│ ↓ │
│ ┌──────────┐ │
│ │Azure DNS │ │
│ │ (域名) │ │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
```
@@ -47,40 +41,33 @@
| 配置项 | 值 |
|--------|-----|
| 终结点 | \`https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1\` |
| 模型 | \`taiji/claude-sonnet-4-5\` |
| API Key | \`sk-mPV5MVVVVvfGSkXA-ASQXQ\` |
| 终结点 | `https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1` |
| 模型 | `taiji/claude-sonnet-4-5` |
| API Key | `sk-mPV5MVVVVvfGSkXA-ASQXQ` |
### Gitee 配置
| 配置项 | 值 |
|--------|-----|
| API URL | \`http://gitee.ath.cx:3000/api/v1\` |
| 用户名 | \`zhanggangyong\` |
| Owner | \`xiaohei\` |
| API URL | `http://gitee.ath.cx:3000/api/v1` |
| 用户名 | `zhanggangyong` |
| Owner | `xiaohei` |
| 模板仓库 | [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS) |
### ACR 配置
| 配置项 | 值 |
|--------|-----|
| Registry | \`agnettaiji.azurecr.io\` |
| Namespace | \`ai-agents\` |
### Azure DNS 配置
| 配置项 | 值 |
|--------|-----|
| DNS Zone | \`taijiagnet.com\` |
| 资源组 | \`taiji-ai-v0\` |
| Registry | `agnettaiji.azurecr.io` |
| Namespace | `ai-agents` |
### WAF 白名单 (重要!)
Gitee 服务器使用雷池 WAF 保护,需要将以下 IP 添加到白名单:
\`\`\`
```
4.144.251.116 (AKS 集群出口 IP)
\`\`\`
```
---
@@ -88,15 +75,15 @@ Gitee 服务器使用雷池 WAF 保护,需要将以下 IP 添加到白名单
| 序号 | 接口 | 方法 | 说明 |
|------|------|------|------|
| 1 | \`/tools/generate\` | POST | 生成单个外部数据工具 |
| 2 | \`/tools/generate-agent\` | POST | 生成完整 Agent 并推送到 Gitee |
| 3 | \`/tools/{tool_ref_id}\` | GET | 获取工具/Agent 信息 |
| 4 | \`/tools/{tool_ref_id}\` | PUT | 更新工具配置 |
| 5 | \`/tools/{tool_ref_id}\` | DELETE | 删除工具 |
| 6 | \`/tools/{tool_ref_id}/test\` | POST | 测试工具连接 |
| 7 | \`/tools/{tool_ref_id}/build-status\` | GET | 查询构建状态 |
| 8 | \`/tools/{tool_ref_id}/deploy\` | POST | 部署 Agent |
| 9 | \`/tools/\` | GET | 列出所有工具/Agent |
| 1 | `/tools/generate` | POST | 生成单个外部数据工具 |
| 2 | `/tools/generate-agent` | POST | 生成完整 Agent 并推送到 Gitee |
| 3 | `/tools/{tool_ref_id}` | GET | 获取工具/Agent 信息 |
| 4 | `/tools/{tool_ref_id}` | PUT | 更新工具配置 |
| 5 | `/tools/{tool_ref_id}` | DELETE | 删除工具 |
| 6 | `/tools/{tool_ref_id}/test` | POST | 测试工具连接 |
| 7 | `/tools/{tool_ref_id}/build-status` | GET | 查询构建状态 |
| 8 | `/tools/{tool_ref_id}/deploy` | POST | 部署 Agent |
| 9 | `/tools/` | GET | 列出所有工具/Agent |
---
@@ -104,9 +91,9 @@ Gitee 服务器使用雷池 WAF 保护,需要将以下 IP 添加到白名单
### 接口
\`\`\`
```
POST /tools/generate
\`\`\`
```
### 请求参数
@@ -127,39 +114,39 @@ POST /tools/generate
#### API Key 认证
\`\`\`json
```json
{
"type": "api_key",
"key": "sk-xxxxxxxxxxxx",
"in": "header",
"name": "X-API-Key"
}
\`\`\`
```
#### Bearer Token 认证
\`\`\`json
```json
{
"type": "bearer",
"key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
\`\`\`
```
#### Basic Auth 认证
\`\`\`json
```json
{
"type": "basic",
"username": "admin",
"password": "password123"
}
\`\`\`
```
### 请求示例
\`\`\`bash
curl -X POST http://20.212.121.126/tools/generate \\
-H "Content-Type: application/json" \\
```bash
curl -X POST http://57.158.136.164/tools/generate \
-H "Content-Type: application/json" \
-d '{
"name": "weather-query",
"description": "查询城市天气",
@@ -183,78 +170,22 @@ curl -X POST http://20.212.121.126/tools/generate \\
}
}
}'
\`\`\`
```
### 响应示例
\`\`\`json
```json
{
"success": true,
"data": {
"tool_ref_id": "tool-weather-query-a49e00fe",
"name": "weather-query",
"description": "查询城市天气",
"created_at": "2026-01-29T10:00:00.000000"
"created_at": "2026-01-26T10:00:00.000000"
},
"message": "工具生成成功"
}
\`\`\`
### 生成的工具代码示例
\`\`\`python
"""
工具: weather-query
描述: 查询城市天气
"""
import os
import json
from typing import Optional, Any
import httpx
async def weather_query(city: str) -> str:
"""
查询城市天气
Args:
city: 城市名称
Returns:
API 响应结果 (JSON 格式)
"""
url = "https://api.weather.com/v1/current"
headers = {}
params = {"city": city}
params["appid"] = os.getenv("TOOL_API_KEY", "")
try:
async with httpx.AsyncClient(timeout=30) as client:
response = await client.request(
method="GET",
url=url,
headers=headers,
params={k: v for k, v in params.items() if v is not None}
)
if response.status_code == 200:
return json.dumps({
"success": True,
"data": response.json()
}, ensure_ascii=False, indent=2)
else:
return json.dumps({
"success": False,
"status_code": response.status_code,
"error": response.text[:500]
}, ensure_ascii=False)
except Exception as e:
return json.dumps({
"success": False,
"error": str(e)
}, ensure_ascii=False)
\`\`\`
```
---
@@ -262,9 +193,9 @@ async def weather_query(city: str) -> str:
### 接口
\`\`\`
```
POST /tools/generate-agent
\`\`\`
```
### 功能描述
@@ -272,7 +203,6 @@ POST /tools/generate-agent
2. 创建 Gitee 仓库并推送代码
3. 包含完整 CI/CD 配置(参考 [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS))
4. 自动触发构建和部署
5. 自动创建 Azure DNS 记录
### 请求参数
@@ -283,13 +213,13 @@ POST /tools/generate-agent
| tools | array | ✅ | 工具配置列表 |
| user_id | string | ✅ | 用户 ID |
| tenant_id | string | ❌ | 租户 ID |
| auto_deploy | boolean | ❌ | 是否自动部署到 AKS(默认 false) |
| auto_deploy | boolean | ❌ | 是否自动部署到 AKS(默认 true) |
### 请求示例
\`\`\`bash
curl -X POST http://20.212.121.126/tools/generate-agent \\
-H "Content-Type: application/json" \\
```bash
curl -X POST http://57.158.136.164/tools/generate-agent \
-H "Content-Type: application/json" \
-d '{
"agent_name": "weather-agent",
"description": "天气查询智能 Agent,支持获取全球城市天气信息",
@@ -324,14 +254,31 @@ curl -X POST http://20.212.121.126/tools/generate-agent \\
}
},
"timeout": 30
},
{
"name": "get_forecast",
"description": "获取城市未来天气预报",
"url": "https://api.openweathermap.org/data/2.5/forecast",
"method": "GET",
"user_id": "user-001",
"request_params": {
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "城市名称",
"required": true
}
}
}
}
]
}'
\`\`\`
```
### 响应示例
\`\`\`json
```json
{
"success": true,
"data": {
@@ -340,78 +287,256 @@ curl -X POST http://20.212.121.126/tools/generate-agent \\
"repo_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-weather-agent-ac3c11",
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest",
"status": "building",
"files_pushed": 9,
"tools_count": 2,
"expected_domain": "agent-weather-agent-ac3c11.taijiagnet.com",
"expected_namespace": "agent-agent-weather-agent-ac3c11"
"files_pushed": 7,
"tools_count": 2
},
"message": "Agent 项目已创建并推送到 Gitee,CI/CD 正在构建中。部署后访问: http://agent-weather-agent-ac3c11.taijiagnet.com"
"message": "Agent 项目已创建并推送到 Gitee,CI/CD 正在构建中"
}
\`\`\`
```
### 生成的项目结构
\`\`\`
```
agent-weather-agent-{id}/
├── .gitea/workflows/
│ └── ci-cd.yaml # CI/CD 配置(构建+部署+DNS)
├── src/
│ └── ci-cd.yaml # CI/CD 配置(构建+部署)
├── tools/
│ ├── __init__.py
│ └── server/
│ ├── __init__.py
│ ├── mcp_server.py # MCP 服务器 + 工具定义
│ └── api_server.py # FastAPI HTTP 服务器
├── run_api_server.py # 入口文件
├── Dockerfile # Docker 构建文件
├── requirements.txt # Python 依赖
└── README.md # 说明文档
\`\`\`
│ ├── get_weather.py # 工具1: 天气查询
│ └── get_forecast.py # 工具2: 天气预报
├── main.py # FastAPI 主程序
├── Dockerfile # Docker 构建文件
├── requirements.txt # Python 依赖
└── README.md # 说明文档
```
---
## 3-9. 其他接口
## 3. 获取工具/Agent 信息
### 3. 获取工具/Agent 信息
### 接口
\`\`\`
```
GET /tools/{tool_ref_id}
\`\`\`
```
### 4. 更新工具配置
### 响应示例
\`\`\`
```json
{
"success": true,
"data": {
"id": "agent-agent-weather-agent-ac3c11",
"name": "weather-agent",
"description": "天气查询智能 Agent",
"status": "building",
"created_at": "2026-01-26T10:00:00.000000",
"repo_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-weather-agent-ac3c11",
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest"
}
}
```
---
## 4. 更新工具配置
### 接口
```
PUT /tools/{tool_ref_id}
\`\`\`
```
### 5. 删除工具
### 请求参数
\`\`\`
| 参数 | 类型 | 说明 |
|------|------|------|
| description | string | 工具描述 |
| url | string | API URL |
| method | string | HTTP 方法 |
| auth | object | 认证配置 |
| request_params | object | 请求参数 |
| timeout | integer | 超时时间 |
---
## 5. 删除工具
### 接口
```
DELETE /tools/{tool_ref_id}?delete_repo=false
\`\`\`
```
### 6. 测试工具连接
### 参数
\`\`\`
| 参数 | 类型 | 说明 |
|------|------|------|
| delete_repo | boolean | 是否同时删除 Gitee 仓库 |
---
## 6. 测试工具连接
### 接口
```
POST /tools/{tool_ref_id}/test
\`\`\`
```
### 7. 查询构建状态
### 请求参数
\`\`\`
```json
{
"test_params": {
"q": "Beijing"
}
}
```
### 响应示例
```json
{
"success": true,
"data": {
"status_code": 200,
"response_time_ms": 156.5,
"content_type": "application/json",
"response_preview": "{\"coord\":{\"lon\":116.4074,\"lat\":39.9042}...}"
},
"message": "工具连接测试成功"
}
```
---
## 7. 查询构建状态
### 接口
```
GET /tools/{tool_ref_id}/build-status
\`\`\`
```
### 8. 部署 Agent
### 功能描述
\`\`\`
1. 查询 Gitee Action 运行状态
2. 查询 ACR 镜像是否已构建完成
### 响应示例
```json
{
"success": true,
"data": {
"tool_ref_id": "agent-agent-weather-agent-ac3c11",
"repo_name": "agent-weather-agent-ac3c11",
"overall_status": "ready",
"gitee_action": {
"status": "completed",
"conclusion": "success",
"run_id": 12345,
"html_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-weather-agent-ac3c11/actions/runs/12345"
},
"acr_image": {
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest",
"exists": true,
"digest": "sha256:abc123...",
"created_at": "2026-01-26T10:05:00Z"
}
}
}
```
### 状态说明
| 状态 | 说明 |
|------|------|
| `building` | 正在构建中 |
| `ready` | 镜像已就绪,可以部署 |
| `build_failed` | 构建失败 |
---
## 8. 部署 Agent
### 接口
```
POST /tools/{tool_ref_id}/deploy
\`\`\`
```
### 9. 列出所有工具/Agent
### 前提条件
\`\`\`
- ACR 中已存在镜像(构建状态为 `ready`)
### 响应示例
```json
{
"success": true,
"data": {
"tool_ref_id": "agent-agent-weather-agent-ac3c11",
"template_name": "agent_weather_agent_ac3c11",
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest",
"status": "deployed",
"message": "模板 agent_weather_agent_ac3c11 已创建,可通过 POST /agents 创建实例"
},
"message": "Agent 模板已创建,可以开始部署实例"
}
```
### 部署实例
模板创建后,使用标准的 Agent 创建接口部署实例:
```bash
curl -X POST http://57.158.136.164/agents \
-H "Content-Type: application/json" \
-d '{
"name": "my-weather-instance",
"template": "agent_weather_agent_ac3c11",
"config": {}
}'
```
---
## 9. 列出所有工具/Agent
### 接口
```
GET /tools/?user_id=user-001
\`\`\`
```
### 响应示例
```json
{
"success": true,
"data": {
"tools": [
{
"id": "agent-agent-weather-agent-ac3c11",
"name": "weather-agent",
"type": "agent",
"status": "ready",
"created_at": "2026-01-26T10:00:00.000000"
},
{
"id": "tool-weather-query-a49e00fe",
"name": "weather-query",
"type": "tool",
"status": "created",
"created_at": "2026-01-26T09:00:00.000000"
}
],
"count": 2
}
}
```
---
@@ -432,9 +557,10 @@ GET /tools/?user_id=user-001
### 步骤 1: 创建 Agent
\`\`\`bash
curl -X POST http://20.212.121.126/tools/generate-agent \\
-H "Content-Type: application/json" \\
```bash
# 发送工具定义,创建 Agent 项目
curl -X POST http://57.158.136.164/tools/generate-agent \
-H "Content-Type: application/json" \
-d '{
"agent_name": "my-custom-agent",
"description": "我的自定义 Agent",
@@ -442,73 +568,165 @@ curl -X POST http://20.212.121.126/tools/generate-agent \\
"auto_deploy": true,
"tools": [...]
}'
\`\`\`
# 返回 agent_ref_id
```
### 步骤 2: 等待构建完成
\`\`\`bash
curl http://20.212.121.126/tools/{agent_ref_id}/build-status
\`\`\`
```bash
# 轮询查询构建状态
curl http://57.158.136.164/tools/{agent_ref_id}/build-status
### 步骤 3: 访问 Agent
# 等待 overall_status 变为 "ready"
```
\`\`\`bash
# 通过域名访问
curl http://{agent-name}.taijiagnet.com/
### 步骤 3: 部署 Agent(如果 auto_deploy=false)
# 健康检查
curl http://{agent-name}.taijiagnet.com/health
```bash
# 手动部署
curl -X POST http://57.158.136.164/tools/{agent_ref_id}/deploy
```
# 调用工具
curl -X POST http://{agent-name}.taijiagnet.com/tools/call \\
-H "Content-Type: application/json" \\
-d '{"tool_name": "get_weather", "parameters": {"q": "Beijing"}}'
### 步骤 4: 创建 Agent 实例
# MCP 调用
curl -X POST http://{agent-name}.taijiagnet.com/mcp \\
-H "Content-Type: application/json" \\
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
\`\`\`
```bash
# 使用生成的模板创建实例
curl -X POST http://57.158.136.164/agents \
-H "Content-Type: application/json" \
-d '{
"name": "my-instance",
"template": "{template_name}",
"config": {}
}'
```
---
## Agent 端点说明
## CI/CD 配置说明
生成的 Agent 提供以下端点:
生成的 CI/CD 配置参考 [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS),包含:
### REST API
1. **构建 Docker 镜像** (ARM64 架构)
2. **推送到 ACR**
3. **部署到 AKS**(如果 `auto_deploy=true`)
| 端点 | 方法 | 说明 |
|------|------|------|
| \`/\` | GET | 服务信息 |
| \`/health\` | GET | 健康检查 |
| \`/tools\` | GET | 列出可用工具 |
| \`/tools/call\` | POST | 调用工具 |
### 生成的 CI/CD 配置示例
### MCP 端点
```yaml
name: CI-CD-AKS
| 端点 | 方法 | 说明 |
|------|------|------|
| \`/mcp\` | POST | MCP HTTP 端点 |
| \`/mcp/sse\` | GET | MCP SSE 端点 |
| \`/mcp/sse\` | POST | MCP SSE POST 端点 |
on:
push:
branches: [ main ]
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ACR
uses: docker/login-action@v3
with:
registry: agnettaiji.azurecr.io
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
push: true
tags: agnettaiji.azurecr.io/ai-agents/{agent-name}:latest
- name: Azure Login
uses: azure/login@v2
with:
creds: |
{
"clientId": "${{ secrets.AZ_CLIENT_ID }}",
"clientSecret": "${{ secrets.AZ_CLIENT_SECRET }}",
"subscriptionId": "${{ secrets.AZ_SUBSCRIPTION_ID }}",
"tenantId": "${{ secrets.AZ_TENANT_ID }}"
}
- name: Get AKS credentials
run: |
az aks get-credentials --resource-group ${{ secrets.AZ_RG }} \
--name ${{ secrets.AZ_AKS }} --overwrite-existing
- name: Deploy to AKS
run: |
kubectl set image deployment/{agent-name} \
{agent-name}=agnettaiji.azurecr.io/ai-agents/{agent-name}:latest \
-n ai-agents || echo "Deployment will be created by agent-manager"
```
### 必需的 Gitee Secrets
在 Gitee 仓库设置中配置以下 Secrets:
| Secret 名称 | 说明 | 值 |
|-------------|------|-----|
| `ACR_LOGIN_SERVER` | ACR 登录服务器 | `agnettaiji.azurecr.io` |
| `ACR_USERNAME` | ACR 用户名 | `agnettaiji` |
| `ACR_PASSWORD` | ACR 密码 | `hDpX5t34N5ZmnKdtqyjYL5co/SnXJrmD20CRpGpWaG+ACRCw2wGM` |
| `AZ_CLIENT_ID` | Azure 服务主体 Client ID | `fb306798-2cfe-4ac9-ba48-eab7bc71bcfe` |
| `AZ_CLIENT_SECRET` | Azure 服务主体密钥 | `cVK8Q~xlfBwm2_t2TC24yrTukWV4F3G~eIjBBa0D` |
| `AZ_TENANT_ID` | Azure 租户 ID | `263c3ff6-1be5-4141-8308-b188464fb297` |
| `AZ_SUBSCRIPTION_ID` | Azure 订阅 ID | `c6c47e4c-f5f4-49f8-b26f-7728862c17d6` |
| `AZ_RG` | AKS 资源组名称 | `taiji-ai-pda` |
| `AZ_AKS` | AKS 集群名称 | `taiji-ai-pda` |
### Gitee Runner 配置
Runner 注册 Token: `1A4jRxECdtycHAuzDHcICNeVmLaWrZ9FvwV2vYY2`
注册命令:
```bash
act_runner register --no-interactive \
--instance "http://gitee.ath.cx:3000" \
--token "1A4jRxECdtycHAuzDHcICNeVmLaWrZ9FvwV2vYY2" \
--name "your-runner-name" \
--labels "self-hosted,linux,arm64"
```
---
## CI/CD Secrets 配置
## 环境变量配置
| Secret 名称 | 说明 |
|-------------|------|
| \`ACR_LOGIN_SERVER\` | ACR 登录服务器 |
| \`ACR_USERNAME\` | ACR 用户名 |
| \`ACR_PASSWORD\` | ACR 密码 |
| \`AZ_CLIENT_ID\` | Azure 服务主体 Client ID |
| \`AZ_CLIENT_SECRET\` | Azure 服务主体密钥 |
| \`AZ_TENANT_ID\` | Azure 租户 ID |
| \`AZ_SUBSCRIPTION_ID\` | Azure 订阅 ID |
| \`AZ_RG\` | AKS 资源组名称 |
| \`AZ_AKS\` | AKS 集群名称 |
| \`AZURE_DNS_ZONE\` | DNS Zone 名称 |
Agent Manager 需要以下环境变量:
### ConfigMap (agent-manager-config)
```yaml
GITEE_API_URL: "http://gitee.ath.cx:3000/api/v1"
GITEE_BASE_URL: "http://gitee.ath.cx:3000"
GITEE_OWNER: "xiaohei"
GITEE_TEMPLATE_REPO: "cicd-AKS"
ACR_REGISTRY: "agnettaiji.azurecr.io"
ACR_NAMESPACE: "ai-agents"
LLM_BASE_URL: "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1"
LLM_MODEL: "taiji/claude-sonnet-4-5"
```
### Secret (agent-manager-secret)
```yaml
GITEE_TOKEN: <base64 encoded token>
GITEE_USERNAME: <base64 encoded: zhanggangyong>
GITEE_PASSWORD: <base64 encoded password>
LLM_API_KEY: <base64 encoded: sk-mPV5MVVVVvfGSkXA-ASQXQ>
```
---
@@ -517,4 +735,3 @@ curl -X POST http://{agent-name}.taijiagnet.com/mcp \\
| 日期 | 版本 | 更新内容 |
|------|------|----------|
| 2026-01-26 | v1.0 | 初始版本 |
| 2026-01-29 | v1.1 | 更新服务地址 (20.212.121.126);添加 DNS 配置说明;添加 generate_tool_code 生成的代码示例;更新项目结构为 src/server/ 目录;添加 expected_domain/expected_namespace 响应字段;添加 Agent 端点说明;简化 Secrets 配置表格 |
+46 -341
View File
@@ -55,8 +55,7 @@ class GiteeManager:
self,
repo_name: str,
description: str = "",
private: bool = False,
auto_init: bool = False
private: bool = False
) -> Dict[str, Any]:
"""
创建 Gitee 仓库
@@ -65,7 +64,6 @@ class GiteeManager:
repo_name: 仓库名称
description: 仓库描述
private: 是否私有
auto_init: 是否自动初始化(默认不初始化,这样可以直接推送文件)
Returns:
仓库信息
@@ -76,68 +74,50 @@ class GiteeManager:
"name": repo_name,
"description": description,
"private": private,
"auto_init": auto_init, # 不自动初始化,后续直接推送文件
"auto_init": True, # 自动初始化
"default_branch": "main"
}
import time
max_retries = 3
for attempt in range(max_retries):
try:
response = requests.post(
url,
json=data,
headers=self._get_headers(),
timeout=30
)
try:
response = requests.post(
url,
json=data,
headers=self._get_headers(),
timeout=30
)
if response.status_code == 201:
repo_info = response.json()
owner = repo_info.get("owner", {}).get("login", self.gitee_username)
logger.info(f"✅ Gitee 仓库创建成功: {owner}/{repo_name}")
return {
"success": True,
"repo_name": repo_name,
"owner": owner,
"clone_url": repo_info.get("clone_url"),
"html_url": repo_info.get("html_url"),
"ssh_url": repo_info.get("ssh_url")
}
elif response.status_code == 409:
# 仓库已存在
owner = self.gitee_username or self.gitee_owner
logger.warning(f"仓库已存在: {repo_name}")
return {
"success": True,
"repo_name": repo_name,
"owner": owner,
"clone_url": f"{self.gitee_base_url}/{owner}/{repo_name}.git",
"html_url": f"{self.gitee_base_url}/{owner}/{repo_name}",
"exists": True
}
else:
error_msg = response.json().get("message", response.text)
logger.error(f"创建仓库失败: {error_msg}")
return {"success": False, "error": error_msg}
if response.status_code == 201:
repo_info = response.json()
owner = repo_info.get("owner", {}).get("login", self.gitee_username)
logger.info(f"✅ Gitee 仓库创建成功: {owner}/{repo_name}")
return {
"success": True,
"repo_name": repo_name,
"owner": owner,
"clone_url": repo_info.get("clone_url"),
"html_url": repo_info.get("html_url"),
"ssh_url": repo_info.get("ssh_url")
}
elif response.status_code == 409:
# 仓库已存在
owner = self.gitee_username or self.gitee_owner
logger.warning(f"仓库已存在: {repo_name}")
return {
"success": True,
"repo_name": repo_name,
"owner": owner,
"clone_url": f"{self.gitee_base_url}/{owner}/{repo_name}.git",
"html_url": f"{self.gitee_base_url}/{owner}/{repo_name}",
"exists": True
}
elif response.status_code == 429:
# API 限流,等待后重试
wait_time = (attempt + 1) * 2
logger.warning(f"API 限流,等待 {wait_time} 秒后重试 ({attempt + 1}/{max_retries})")
time.sleep(wait_time)
continue
else:
try:
error_msg = response.json().get("message", response.text)
except:
error_msg = response.text[:200] if response.text else f"HTTP {response.status_code}"
logger.error(f"创建仓库失败: {error_msg}")
return {"success": False, "error": error_msg}
except Exception as e:
logger.error(f"创建仓库异常 (尝试 {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1:
time.sleep(2)
continue
return {"success": False, "error": str(e)}
return {"success": False, "error": "API 限流,请稍后重试"}
except Exception as e:
logger.error(f"创建仓库异常: {e}")
return {"success": False, "error": str(e)}
def push_files(
self,
@@ -148,7 +128,7 @@ class GiteeManager:
owner: str = None
) -> Dict[str, Any]:
"""
推送文件到仓库(使用 Git Data API 批量提交)
推送文件到仓库
Args:
repo_name: 仓库名称
@@ -166,51 +146,19 @@ class GiteeManager:
repo_owner = owner or self.gitee_username or self.gitee_owner
results = {"success": True, "files": [], "owner": repo_owner}
logger.info(f"开始推送 {len(files)} 个文件到 {repo_owner}/{repo_name}")
# 检查仓库是否为空
is_empty = self._is_repo_empty(repo_owner, repo_name)
if is_empty:
# 空仓库:使用初始提交方式
logger.info("仓库为空,使用初始提交方式")
init_result = self._push_initial_commit(repo_owner, repo_name, files, commit_message, branch)
if init_result.get("success"):
logger.info(f"✅ 初始提交成功: {len(files)} 个文件")
results["files"] = [{"path": p, "status": "success"} for p in files.keys()]
return results
else:
logger.error(f"初始提交失败: {init_result.get('error')}")
results["success"] = False
results["error"] = init_result.get("error")
return results
# 非空仓库:尝试使用批量提交 API
try:
batch_result = self._push_files_batch(repo_owner, repo_name, files, commit_message, branch)
if batch_result.get("success"):
logger.info(f"✅ 批量推送成功: {len(files)} 个文件")
results["files"] = [{"path": p, "status": "success"} for p in files.keys()]
return results
else:
logger.warning(f"批量推送失败,尝试逐个推送: {batch_result.get('error')}")
except Exception as e:
logger.warning(f"批量推送异常,尝试逐个推送: {e}")
# 方法2:逐个推送文件
for file_path, content in files.items():
try:
# 使用 Contents API 创建/更新文件
url = f"{self.gitee_api_url}/repos/{repo_owner}/{repo_name}/contents/{file_path}"
# 检查文件是否存在(获取 SHA)
# 检查文件是否存在
check_response = requests.get(url, headers=self._get_headers(), timeout=10)
sha = None
if check_response.status_code == 200:
sha = check_response.json().get("sha")
data = {
"message": f"{commit_message} - {file_path}",
"message": commit_message,
"content": base64.b64encode(content.encode()).decode(),
"branch": branch
}
@@ -226,7 +174,7 @@ class GiteeManager:
)
# 添加短暂延迟避免 API 限流
time.sleep(0.3)
time.sleep(0.5)
if response.status_code in [200, 201]:
results["files"].append({
@@ -235,17 +183,14 @@ class GiteeManager:
})
logger.info(f"✅ 文件推送成功: {file_path}")
else:
error_msg = response.text[:200]
logger.error(f"❌ 文件推送失败: {file_path}, 状态码: {response.status_code}, 错误: {error_msg}")
results["files"].append({
"path": file_path,
"status": "failed",
"error": error_msg
"error": response.text
})
results["success"] = False
except Exception as e:
logger.error(f"❌ 文件推送异常: {file_path}, 错误: {e}")
results["files"].append({
"path": file_path,
"status": "failed",
@@ -253,248 +198,8 @@ class GiteeManager:
})
results["success"] = False
logger.info(f"推送完成: 成功 {sum(1 for f in results['files'] if f['status'] == 'success')}/{len(files)} 个文件")
return results
def _push_files_batch(
self,
owner: str,
repo_name: str,
files: Dict[str, str],
commit_message: str,
branch: str
) -> Dict[str, Any]:
"""
使用 Git Data API 批量推送文件
流程:
1. 获取当前分支的最新 commit SHA
2. 获取该 commit 的 tree SHA
3. 创建新的 blobs
4. 创建新的 tree
5. 创建新的 commit
6. 更新分支引用
"""
try:
headers = self._get_headers()
base_url = f"{self.gitee_api_url}/repos/{owner}/{repo_name}"
# 1. 获取当前分支的最新 commit
# Gitea API 返回的是数组
ref_url = f"{base_url}/git/refs/heads/{branch}"
ref_response = requests.get(ref_url, headers=headers, timeout=10)
if ref_response.status_code != 200:
return {"success": False, "error": f"获取分支引用失败: {ref_response.text}"}
ref_data = ref_response.json()
# 处理 Gitea API 可能返回数组或对象的情况
if isinstance(ref_data, list):
if not ref_data:
return {"success": False, "error": "无法获取分支引用"}
ref_data = ref_data[0]
latest_commit_sha = ref_data.get("object", {}).get("sha")
if not latest_commit_sha:
return {"success": False, "error": "无法获取最新 commit SHA"}
logger.info(f"获取到最新 commit SHA: {latest_commit_sha[:8]}...")
# 2. 获取该 commit 的 tree
commit_url = f"{base_url}/git/commits/{latest_commit_sha}"
commit_response = requests.get(commit_url, headers=headers, timeout=10)
if commit_response.status_code != 200:
return {"success": False, "error": f"获取 commit 信息失败: {commit_response.text}"}
commit_data = commit_response.json()
# Gitea API: tree 在 commit.tree 中
base_tree_sha = None
if commit_data.get("commit", {}).get("tree"):
base_tree_sha = commit_data["commit"]["tree"].get("sha")
elif commit_data.get("tree"):
base_tree_sha = commit_data["tree"].get("sha")
if not base_tree_sha:
logger.error(f"无法获取 tree SHA,commit 数据: {commit_data}")
return {"success": False, "error": "无法获取 tree SHA"}
logger.info(f"获取到 base tree SHA: {base_tree_sha[:8]}...")
# 3. 创建 tree 项目
tree_items = []
for file_path, content in files.items():
tree_items.append({
"path": file_path,
"mode": "100644",
"type": "blob",
"content": content
})
# 4. 创建新 tree
tree_url = f"{base_url}/git/trees"
tree_data = {
"base_tree": base_tree_sha,
"tree": tree_items
}
logger.info(f"创建新 tree,包含 {len(tree_items)} 个文件...")
tree_response = requests.post(tree_url, json=tree_data, headers=headers, timeout=120)
if tree_response.status_code not in [200, 201]:
return {"success": False, "error": f"创建 tree 失败: {tree_response.text[:200]}"}
new_tree_sha = tree_response.json().get("sha")
logger.info(f"新 tree SHA: {new_tree_sha[:8]}...")
# 5. 创建新 commit
commit_create_url = f"{base_url}/git/commits"
commit_create_data = {
"message": commit_message,
"tree": new_tree_sha,
"parents": [latest_commit_sha]
}
logger.info(f"创建新 commit...")
commit_create_response = requests.post(commit_create_url, json=commit_create_data, headers=headers, timeout=30)
if commit_create_response.status_code not in [200, 201]:
return {"success": False, "error": f"创建 commit 失败: {commit_create_response.text[:200]}"}
new_commit_sha = commit_create_response.json().get("sha")
logger.info(f"新 commit SHA: {new_commit_sha[:8]}...")
# 6. 更新分支引用
update_ref_data = {
"sha": new_commit_sha,
"force": False
}
logger.info(f"更新分支引用到新 commit...")
update_ref_response = requests.patch(ref_url, json=update_ref_data, headers=headers, timeout=10)
if update_ref_response.status_code not in [200, 201]:
return {"success": False, "error": f"更新分支引用失败: {update_ref_response.text[:200]}"}
logger.info(f"✅ 批量推送成功,commit: {new_commit_sha[:8]}")
return {"success": True, "commit_sha": new_commit_sha}
except Exception as e:
import traceback
logger.error(f"批量推送异常: {e}")
logger.error(traceback.format_exc())
return {"success": False, "error": str(e)}
def _is_repo_empty(self, owner: str, repo_name: str) -> bool:
"""检查仓库是否为空"""
try:
url = f"{self.gitee_api_url}/repos/{owner}/{repo_name}"
response = requests.get(url, headers=self._get_headers(), timeout=10)
if response.status_code == 200:
repo_data = response.json()
return repo_data.get("empty", True)
return True
except Exception as e:
logger.warning(f"检查仓库是否为空失败: {e}")
return True
def _push_initial_commit(
self,
owner: str,
repo_name: str,
files: Dict[str, str],
commit_message: str,
branch: str
) -> Dict[str, Any]:
"""
推送初始提交到空仓库
空仓库不支持 Git Data API,需要先用 Contents API 创建第一个文件
然后再使用 Git Data API 批量推送其余文件
"""
import time
try:
headers = self._get_headers()
base_url = f"{self.gitee_api_url}/repos/{owner}/{repo_name}"
logger.info(f"开始初始提交,共 {len(files)} 个文件...")
# 找出 README.md 作为初始文件(或者使用第一个文件)
init_file_path = None
init_file_content = None
if "README.md" in files:
init_file_path = "README.md"
init_file_content = files["README.md"]
else:
# 使用第一个文件
init_file_path = list(files.keys())[0]
init_file_content = files[init_file_path]
# 1. 使用 Contents API 创建第一个文件(初始化仓库)
init_url = f"{base_url}/contents/{init_file_path}"
init_data = {
"message": f"{commit_message} - {init_file_path}",
"content": base64.b64encode(init_file_content.encode()).decode(),
"branch": branch
}
logger.info(f"创建初始文件: {init_file_path}")
init_response = requests.post(init_url, json=init_data, headers=headers, timeout=30)
if init_response.status_code not in [200, 201]:
return {"success": False, "error": f"创建初始文件失败: {init_response.text[:300]}"}
logger.info(f"✓ 初始文件创建成功: {init_file_path}")
time.sleep(0.5)
# 2. 推送剩余文件
remaining_files = {k: v for k, v in files.items() if k != init_file_path}
if remaining_files:
logger.info(f"使用批量方式推送剩余 {len(remaining_files)} 个文件...")
# 现在仓库已初始化,使用 Git Data API 批量推送
batch_result = self._push_files_batch(owner, repo_name, remaining_files, commit_message, branch)
if not batch_result.get("success"):
# 批量失败,逐个推送
logger.warning(f"批量推送失败: {batch_result.get('error')}, 尝试逐个推送")
for file_path, content in remaining_files.items():
try:
file_url = f"{base_url}/contents/{file_path}"
file_data = {
"message": f"{commit_message} - {file_path}",
"content": base64.b64encode(content.encode()).decode(),
"branch": branch
}
file_response = requests.post(file_url, json=file_data, headers=headers, timeout=30)
if file_response.status_code in [200, 201]:
logger.info(f" ✓ {file_path}")
else:
logger.error(f" ✗ {file_path}: {file_response.text[:100]}")
time.sleep(0.3)
except Exception as e:
logger.error(f" ✗ {file_path}: {e}")
logger.info(f"✅ 初始提交完成")
return {"success": True}
except Exception as e:
import traceback
logger.error(f"初始提交异常: {e}")
logger.error(traceback.format_exc())
return {"success": False, "error": str(e)}
def get_action_status(self, repo_name: str) -> Dict[str, Any]:
"""
获取 Gitee Action 运行状态
+11
View File
@@ -10,3 +10,14 @@ data:
AZURE_DNS_ZONE: "taijiagnet.com"
AZURE_SUBSCRIPTION_ID: "your-subscription-id"
AZURE_RESOURCE_GROUP: "your-resource-group"
# Gitee 配置(非敏感信息)
GITEE_API_URL: "http://gitee.ath.cx:3000/api/v1"
GITEE_BASE_URL: "http://gitee.ath.cx:3000"
GITEE_OWNER: "xiaohei"
GITEE_USERNAME: "zhanggangyong"
GITEE_TEMPLATE_REPO: "cicd-AKS"
# ACR 配置
ACR_REGISTRY: "agnettaiji.azurecr.io"
ACR_NAMESPACE: "ai-agents"
+11
View File
@@ -61,6 +61,17 @@ spec:
secretKeyRef:
name: agent-manager-secret
key: AZURE_CLIENT_SECRET
# Gitee 凭据
- name: GITEE_TOKEN
valueFrom:
secretKeyRef:
name: agent-manager-secret
key: GITEE_TOKEN
- name: GITEE_PASSWORD
valueFrom:
secretKeyRef:
name: agent-manager-secret
key: GITEE_PASSWORD
# 挂载 kubeconfig(用于管理其他 Agent)
volumeMounts:
+4
View File
@@ -10,5 +10,9 @@ stringData:
AZURE_CLIENT_ID: "your-client-id"
AZURE_CLIENT_SECRET: "your-client-secret"
# Gitee 凭据(敏感信息)
GITEE_TOKEN: "your-gitee-token"
GITEE_PASSWORD: "your-gitee-password"
# 数据库密码(如果需要单独管理)
# DB_PASSWORD: "By@123456."
+18 -1
View File
@@ -30,6 +30,10 @@ AZURE_CLIENT_SECRET="${AZURE_CLIENT_SECRET:-your-client-secret}"
AZURE_SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID:-your-subscription-id}"
AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-your-resource-group}"
# Gitee 配置(需要替换为实际值)
GITEE_TOKEN="${GITEE_TOKEN:-your-gitee-token}"
GITEE_PASSWORD="${GITEE_PASSWORD:-your-gitee-password}"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Agent Manager K8s 部署 (ARM64)${NC}"
echo -e "${BLUE}========================================${NC}"
@@ -154,7 +158,18 @@ update_config() {
fi
fi
# 创建临时 secret 文件
# 检查 Gitee 凭据
if [ "$GITEE_TOKEN" = "your-gitee-token" ]; then
print_warning "请设置 GITEE_TOKEN 环境变量(创建 Agent 仓库必需)"
print_warning " export GITEE_TOKEN=your-actual-token"
read -p "是否继续部署(不含 Gitee 仓库功能)?[y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# 创建临时 secret 文件(包含 Azure 和 Gitee 凭据)
cat > /tmp/agent-manager-secret.yaml <<EOF
apiVersion: v1
kind: Secret
@@ -166,6 +181,8 @@ stringData:
AZURE_TENANT_ID: "${AZURE_TENANT_ID}"
AZURE_CLIENT_ID: "${AZURE_CLIENT_ID}"
AZURE_CLIENT_SECRET: "${AZURE_CLIENT_SECRET}"
GITEE_TOKEN: "${GITEE_TOKEN}"
GITEE_PASSWORD: "${GITEE_PASSWORD}"
EOF
kubectl apply -f ${K8S_DIR}/agent-manager-configmap.yaml