""" Agent 代码生成模块 使用 AI 根据工具定义生成 Pydantic Agent 代码 基于 agent_templates/agents/_template 框架 包含完整 CI/CD 配置 """ import os import json import logging import re import requests from typing import Dict, List, Optional, Any from datetime import datetime logger = logging.getLogger(__name__) class AgentCodeGenerator: """Agent 代码生成器 - 基于 _template 框架""" def __init__(self): # LLM 配置 (用于 AI 生成代码) self.llm_base_url = os.getenv( "LLM_BASE_URL", "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1" ) self.llm_api_key = os.getenv("LLM_API_KEY", "sk-mPV5MVVVVvfGSkXA-ASQXQ") self.llm_model = os.getenv("LLM_MODEL", "taiji/claude-sonnet-4-5") # ACR 配置 self.acr_registry = os.getenv("ACR_REGISTRY", "agnettaiji.azurecr.io") self.acr_namespace = os.getenv("ACR_NAMESPACE", "ai-agents") # CI/CD Secrets 默认值(用于文档说明) self.cicd_secrets = { "ACR_LOGIN_SERVER": "agnettaiji.azurecr.io", "ACR_USERNAME": "agnettaiji", "ACR_PASSWORD": "hDpX5t34N5ZmnKdtqyjYL5co/SnXJrmD20CRpGpWaG+ACRCw2wGM", "AZ_CLIENT_ID": "f2dd1cb2-02f6-4efb-bc72-d148f6e01545", "AZ_CLIENT_SECRET": "iym8Q~n5iHfqTTXofnKN8QSomhrUXmXXOBH9ubgq", "AZ_TENANT_ID": "263c3ff6-1be5-4141-8308-b188464fb297", "AZ_SUBSCRIPTION_ID": "45d7a360-af09-40fc-9afc-56dc475245ec", "AZ_RG": "taiji-ai-pda", "AZ_AKS": "taiji-ai-pda", "AZURE_DNS_ZONE": "taijiagnet.com" } def _call_llm(self, prompt: str, system_prompt: str = None) -> str: """调用 LLM 生成代码""" messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) messages.append({"role": "user", "content": prompt}) try: response = requests.post( f"{self.llm_base_url}/chat/completions", headers={ "Authorization": f"Bearer {self.llm_api_key}", "Content-Type": "application/json" }, json={ "model": self.llm_model, "messages": messages, "temperature": 0.2, "max_tokens": 8000 }, timeout=120 ) if response.status_code == 200: result = response.json() return result["choices"][0]["message"]["content"] else: logger.error(f"LLM 调用失败: {response.text}") return None except Exception as e: logger.error(f"LLM 调用异常: {e}") return None def _convert_name_to_python(self, name: str) -> str: """将工具名称转换为 Python 函数名""" name = re.sub(r'[^a-zA-Z0-9_]', '_', name) if name and name[0].isdigit(): name = '_' + name return name.lower() def _json_type_to_python(self, json_type: str) -> str: """JSON Schema 类型转 Python 类型""" type_map = { "string": "str", "integer": "int", "number": "float", "boolean": "bool", "array": "list", "object": "dict" } return type_map.get(json_type, "Any") def generate_mcp_server( self, agent_name: str, description: str, tools_config: List[Dict] ) -> str: """ 生成 MCP 服务器代码 (mcp_server.py) 基于 _template/src/server/mcp_server.py 框架 """ # 生成工具函数 tool_functions = [] tool_map_entries = [] tool_list_entries = [] for tool in tools_config: name = tool.get("name", "custom_tool") func_name = self._convert_name_to_python(name) desc = tool.get("description", "") url = tool.get("url", "") method = tool.get("method", "GET").upper() auth = tool.get("auth", {}) request_params = tool.get("request_params", {}) timeout = tool.get("timeout", 30) # 构建参数 params = [] params_doc = [] properties = {} required_params = [] # 支持两种格式: # 1. {"properties": {"symbol": {...}}} # 2. {"symbol": {...}} (直接参数格式) param_props = request_params if request_params and request_params.get("properties"): param_props = request_params["properties"] elif request_params and not any(k in request_params for k in ["type", "required", "description"]): # 直接参数格式 param_props = request_params else: param_props = {} if param_props: for p_name, p_info in param_props.items(): if not isinstance(p_info, dict): continue p_type = self._json_type_to_python(p_info.get("type", "string")) p_desc = p_info.get("description", "") is_required = p_info.get("required", False) default = p_info.get("default") if is_required: params.append(f"{p_name}: {p_type}") required_params.append(p_name) else: default_val = f'"{default}"' if isinstance(default, str) else (default if default is not None else "None") params.append(f"{p_name}: Optional[{p_type}] = {default_val}") params_doc.append(f" {p_name}: {p_desc}") properties[p_name] = {"type": p_info.get("type", "string"), "description": p_desc} params_str = ", ".join(params) if params else "" params_doc_str = "\n".join(params_doc) if params_doc else " 无参数" # 生成认证代码 auth_headers = self._get_auth_headers_code(auth) # 构建参数字典代码 params_dict_code = "" if param_props: params_dict_code = "params = {" for p_name in param_props.keys(): if isinstance(param_props[p_name], dict): params_dict_code += f'"{p_name}": {p_name}, ' params_dict_code = params_dict_code.rstrip(", ") + "}" else: params_dict_code = "params = {}" # API Key in query if auth and auth.get("type") == "api_key" and auth.get("in") == "query": key_name = auth.get("name", "apikey") params_dict_code += f'\n params["{key_name}"] = os.getenv("TOOL_API_KEY", "")' # 生成函数代码 func_code = f''' @server.tool() async def {func_name}({params_str}) -> str: """ {desc} Args: {params_doc_str} Returns: API 响应结果 (JSON 格式) """ import httpx url = "{url}" {auth_headers} {params_dict_code} try: async with httpx.AsyncClient(timeout={timeout}) as client: response = await client.request( method="{method}", 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() if response.headers.get("content-type", "").startswith("application/json") else response.text }}, 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: # 使用 AI Agent 作为后备 result = await get_agent().run(f"请帮我处理这个请求: {params}") return json.dumps({{ "success": True, "source": "ai_agent", "result": result.output }}, ensure_ascii=False, indent=2) ''' tool_functions.append(func_code) tool_map_entries.append(f" '{func_name}': {func_name},") tool_list_entries.append(f''' {{ "name": "{func_name}", "description": "{desc}", "inputSchema": {{ "type": "object", "properties": {json.dumps(properties, ensure_ascii=False)}, "required": {json.dumps(required_params)} }} }},''') tool_functions_str = "\n".join(tool_functions) tool_map_str = "\n".join(tool_map_entries) tool_list_str = "\n".join(tool_list_entries) # 生成系统提示词 system_prompt = self._generate_system_prompt(agent_name, description, tools_config) return f'''""" MCP 服务器 - {agent_name} {description} 使用 Pydantic AI 和 FastMCP 框架。 自动生成时间: {datetime.now().isoformat()} """ import json import os from typing import Optional from mcp.server.fastmcp import FastMCP from pydantic_ai import Agent # ==================== 配置 ==================== # LiteLLM Gateway 配置 _BASE_URL = os.getenv('OPENAI_BASE_URL', os.getenv('LLM_BASE_URL', 'https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1')) _API_KEY = os.getenv('OPENAI_API_KEY', 'sk') os.environ.setdefault('OPENAI_API_KEY', _API_KEY) os.environ.setdefault('OPENAI_BASE_URL', _BASE_URL) # 模型名称(pydantic_ai 需要 openai: 前缀) def _get_model_name() -> str: model = os.getenv('MODEL_NAME', os.getenv('LITELLM_MODEL', 'taiji/gpt-4o-mini')) return model if ':' in model else f'openai:{{model}}' MODEL_NAME = _get_model_name() # ==================== MCP 服务器 ==================== server = FastMCP('{agent_name}') # 系统提示词 SYSTEM_PROMPT = """{system_prompt}""" def get_agent() -> Agent: """创建 Agent 实例(每次调用使用最新的 API Key)""" return Agent(MODEL_NAME, system_prompt=SYSTEM_PROMPT) # ==================== MCP 工具定义 ==================== {tool_functions_str} # ==================== 工具映射(供 API 使用)==================== TOOL_MAP = {{ {tool_map_str} }} TOOL_LIST = [ {tool_list_str} ] if __name__ == '__main__': server.run() ''' def _get_auth_headers_code(self, auth: Dict) -> str: """生成认证头代码""" if not auth: return "headers = {}" auth_type = auth.get("type", "") if auth_type == "api_key": key = auth.get("key", "") location = auth.get("in", "header") name = auth.get("name", "X-API-Key") if location == "header": return f'headers = {{"{name}": os.getenv("TOOL_API_KEY", "{key}")}}' else: return "headers = {} # API Key 在查询参数中" elif auth_type == "bearer": key = auth.get("key", "") return f'headers = {{"Authorization": f"Bearer {{os.getenv(\'TOOL_API_KEY\', \'{key}\')}}" }}' elif auth_type == "basic": username = auth.get("username", "") password = auth.get("password", "") return f'''import base64 _creds = base64.b64encode(f"{{os.getenv('TOOL_USERNAME', '{username}')}}:{{os.getenv('TOOL_PASSWORD', '{password}')}}".encode()).decode() headers = {{"Authorization": f"Basic {{_creds}}"}}''' return "headers = {}" def _generate_system_prompt( self, agent_name: str, description: str, tools_config: List[Dict] ) -> str: """生成智能系统提示词""" tools_desc = "\n".join([ f"- {t.get('name')}: {t.get('description', '')}" for t in tools_config ]) return f'''你是 {agent_name},一个专业的 AI 智能助手。 ## 角色定位 {description} ## 可用工具 {tools_desc} ## 工作原则 1. 理解用户意图:仔细分析用户的请求,确保准确理解需求 2. 选择合适工具:根据需求选择最合适的工具来完成任务 3. 清晰反馈:以用户友好的方式呈现结果 4. 错误处理:遇到问题时提供有用的错误信息和建议 ## 响应格式 - 对于数据查询:返回结构化的 JSON 数据 - 对于操作请求:返回操作状态和结果 - 始终使用中文与用户交流(除非用户使用其他语言)''' def generate_api_server(self, agent_name: str, description: str) -> str: """ 生成 API 服务器代码 (api_server.py) 基于 _template/src/server/api_server.py 框架 """ return f'''""" HTTP API 服务器 - {agent_name} {description} 提供 REST API 和 MCP HTTP/SSE 端点。 自动生成时间: {datetime.now().isoformat()} """ import json import uuid import os from typing import Optional, Dict, Any, AsyncGenerator from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException, Request, Header, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse, JSONResponse from pydantic import BaseModel, Field from .mcp_server import TOOL_MAP, TOOL_LIST # ==================== 配置 ==================== SERVER_NAME = "{agent_name}" # ==================== FastAPI 应用 ==================== @asynccontextmanager async def lifespan(app: FastAPI): print(f"🚀 {{SERVER_NAME}} 启动") yield print(f"🛑 {{SERVER_NAME}} 关闭") app = FastAPI( title=SERVER_NAME, description="{description}", version="1.0.0", lifespan=lifespan ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ==================== API Key 验证 ==================== async def verify_api_key( api_key: Optional[str] = Header(None, alias="api-key"), authorization: Optional[str] = Header(None) ) -> str: """验证 API Key""" if api_key and api_key.strip() and api_key.strip() != "sk": return api_key.strip() if authorization: key = authorization[7:].strip() if authorization.startswith("Bearer ") else authorization.strip() if key and key != "sk": return key # 允许无 API Key 访问(使用默认配置) return os.getenv("OPENAI_API_KEY", "sk") def get_api_key_from_request(request: Request) -> Optional[str]: """从请求头提取 API Key(不验证)""" api_key = request.headers.get("api-key") or request.headers.get("api_key") if not api_key: auth = request.headers.get("Authorization") if auth: api_key = auth[7:] if auth.startswith("Bearer ") else auth return api_key or os.getenv("OPENAI_API_KEY", "sk") # ==================== 健康检查 ==================== @app.get("/") async def root(): return {{ "service": SERVER_NAME, "status": "running", "tools": list(TOOL_MAP.keys()) }} @app.get("/health") async def health(): return {{"status": "healthy", "service": SERVER_NAME}} # ==================== MCP 端点 ==================== sessions: Dict[str, Dict] = {{}} async def handle_mcp_request(data: Dict, session_id: str = None, api_key: str = None) -> Dict: """处理 MCP JSON-RPC 请求""" method = data.get("method") params = data.get("params", {{}}) req_id = data.get("id") try: if method == "initialize": session_id = session_id or str(uuid.uuid4()) sessions[session_id] = {{"initialized": True}} return {{ "jsonrpc": "2.0", "id": req_id, "result": {{ "protocolVersion": "2024-11-05", "capabilities": {{"tools": {{}}}}, "serverInfo": {{"name": SERVER_NAME, "version": "1.0.0"}} }} }} elif method == "tools/list": return {{"jsonrpc": "2.0", "id": req_id, "result": {{"tools": TOOL_LIST}}}} elif method == "tools/call": tool_name = params.get("name") args = params.get("arguments", {{}}) if tool_name not in TOOL_MAP: raise ValueError(f"Unknown tool: {{tool_name}}") # 设置 API Key 到环境变量 old_key = os.environ.get('OPENAI_API_KEY') if api_key: os.environ['OPENAI_API_KEY'] = api_key try: result = await TOOL_MAP[tool_name](**args) finally: if old_key: os.environ['OPENAI_API_KEY'] = old_key return {{ "jsonrpc": "2.0", "id": req_id, "result": {{"content": [{{"type": "text", "text": str(result)}}]}} }} elif method == "ping": return {{"jsonrpc": "2.0", "id": req_id, "result": {{}}}} else: raise ValueError(f"Unknown method: {{method}}") except Exception as e: return {{"jsonrpc": "2.0", "id": req_id, "error": {{"code": -32603, "message": str(e)}}}} @app.post("/mcp") async def mcp_endpoint(request: Request): """MCP HTTP 端点""" try: body = await request.json() session_id = request.headers.get("x-mcp-session-id") api_key = get_api_key_from_request(request) response = await handle_mcp_request(body, session_id, api_key) return JSONResponse(content=response, headers={{"x-mcp-session-id": session_id or ""}}) except Exception as e: return JSONResponse(status_code=400, content={{"jsonrpc": "2.0", "error": {{"code": -32700, "message": str(e)}}}}) @app.get("/mcp/sse") async def mcp_sse(request: Request): """MCP SSE 端点""" session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) async def stream() -> AsyncGenerator[str, None]: yield f"data: {{json.dumps({{'type': 'connection', 'sessionId': session_id}})}}\\n\\n" import asyncio while True: await asyncio.sleep(30) yield f"data: {{json.dumps({{'type': 'ping'}})}}\\n\\n" return StreamingResponse(stream(), media_type="text/event-stream", headers={{"Cache-Control": "no-cache", "x-mcp-session-id": session_id}}) @app.post("/mcp/sse") async def mcp_sse_post(request: Request): """MCP SSE POST 端点""" try: body = await request.json() session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) api_key = get_api_key_from_request(request) async def stream() -> AsyncGenerator[str, None]: response = await handle_mcp_request(body, session_id, api_key) yield f"data: {{json.dumps(response)}}\\n\\n" return StreamingResponse(stream(), media_type="text/event-stream", headers={{"Cache-Control": "no-cache", "x-mcp-session-id": session_id}}) except Exception as e: return JSONResponse(status_code=400, content={{"jsonrpc": "2.0", "error": {{"code": -32700, "message": str(e)}}}}) # ==================== 业务 API ==================== class ToolCallRequest(BaseModel): """工具调用请求""" tool_name: str = Field(..., description="工具名称") parameters: Dict[str, Any] = Field(default={{}}, description="工具参数") class ToolCallResponse(BaseModel): """工具调用响应""" success: bool result: Optional[Any] = None error: Optional[str] = None @app.get("/tools") async def list_tools(): """列出可用工具""" return {{ "tools": [ {{"name": t["name"], "description": t["description"]}} for t in TOOL_LIST ] }} @app.post("/tools/call", response_model=ToolCallResponse) async def call_tool(request: ToolCallRequest, api_key: str = Depends(verify_api_key)): """调用工具""" if request.tool_name not in TOOL_MAP: raise HTTPException(status_code=404, detail=f"工具 {{request.tool_name}} 不存在") try: old_key = os.environ.get('OPENAI_API_KEY') os.environ['OPENAI_API_KEY'] = api_key try: result = await TOOL_MAP[request.tool_name](**request.parameters) return ToolCallResponse(success=True, result=json.loads(result) if isinstance(result, str) else result) finally: if old_key: os.environ['OPENAI_API_KEY'] = old_key except Exception as e: return ToolCallResponse(success=False, error=str(e)) if __name__ == '__main__': import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) ''' def generate_run_api_server(self) -> str: """生成入口文件 run_api_server.py""" return '''#!/usr/bin/env python """启动 API 服务器""" import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) if __name__ == '__main__': from src.server.api_server import app import uvicorn import os host = os.getenv('API_HOST', '0.0.0.0') port = int(os.getenv('API_PORT', '8000')) print(f"🚀 启动 Agent API: http://{host}:{port}") uvicorn.run(app, host=host, port=port, log_level="info") ''' def generate_dockerfile(self, agent_name: str) -> str: """生成 Dockerfile (基于 _template)""" return f'''FROM python:3.12-slim WORKDIR /app ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 # 安装系统依赖 RUN apt-get update && apt-get install -y gcc curl && rm -rf /var/lib/apt/lists/* # 复制依赖文件 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 暴露端口 EXPOSE 8000 # 健康检查 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"] ''' def generate_requirements(self) -> str: """生成 requirements.txt""" return '''# Pydantic AI pydantic-ai>=0.0.14 # MCP mcp>=0.9.0 fastmcp>=0.1.0 # FastAPI fastapi>=0.109.0 uvicorn[standard]>=0.27.0 # HTTP Client httpx>=0.25.0 aiohttp>=3.9.0 # 其他 python-dotenv>=1.0.0 ''' def generate_gitea_action(self, agent_name: str, auto_deploy: bool = True) -> str: """ 生成 Gitea Actions CI/CD 配置 参考: http://gitee.ath.cx:3000/xiaohei/cicd-AKS 使用预配置的密钥 """ k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") image_repo = f"{self.acr_namespace}/{k8s_name}" deploy_step = "" if auto_deploy: deploy_step = f''' - name: Login Azure run: | az login --service-principal \\ -u "$AZ_CLIENT_ID" \\ -p "$AZ_CLIENT_SECRET" \\ --tenant "$AZ_TENANT_ID" az account set --subscription "$AZ_SUBSCRIPTION_ID" - name: Get AKS Credentials run: | az aks get-credentials \\ --resource-group "$AZ_RG" \\ --name "$AZ_AKS" \\ --overwrite-existing - name: Setup ACR Pull Secret run: | # 创建 Agent 专用命名空间 kubectl create namespace agent-{k8s_name} --dry-run=client -o yaml | kubectl apply -f - # 创建或更新 ACR 拉取 Secret kubectl create secret docker-registry acr-secret \\ --docker-server=$ACR_LOGIN_SERVER \\ --docker-username=$ACR_USERNAME \\ --docker-password=$ACR_PASSWORD \\ -n agent-{k8s_name} --dry-run=client -o yaml | kubectl apply -f - - name: Deploy to AKS run: | # 删除旧的 deployment(如果存在) kubectl delete deployment {k8s_name} -n agent-{k8s_name} --ignore-not-found=true # 创建 deployment YAML cat </dev/null) if [ -n "$EXTERNAL_IP" ]; then echo "✅ Agent LoadBalancer IP: $EXTERNAL_IP" echo "EXTERNAL_IP=$EXTERNAL_IP" >> $GITHUB_ENV break fi sleep 10 done - name: Create DNS Record if: env.EXTERNAL_IP != '' run: | echo "创建 DNS 记录: {k8s_name}.$AZURE_DNS_ZONE -> $EXTERNAL_IP" # 获取 Azure 访问令牌 ACCESS_TOKEN=$(az account get-access-token --query accessToken -o tsv) # 创建 DNS A 记录 DNS_URL="https://management.azure.com/subscriptions/$AZ_SUBSCRIPTION_ID/resourceGroups/$AZ_RG/providers/Microsoft.Network/dnsZones/$AZURE_DNS_ZONE/A/{k8s_name}?api-version=2018-05-01" curl -X PUT "$DNS_URL" \\ -H "Authorization: Bearer $ACCESS_TOKEN" \\ -H "Content-Type: application/json" \\ -d '{{ "properties": {{ "TTL": 300, "ARecords": [{{"ipv4Address": "'"$EXTERNAL_IP"'"}}] }} }}' echo "" echo "✅ DNS 记录创建成功!" echo "🌐 Agent 访问地址: http://{k8s_name}.$AZURE_DNS_ZONE" echo "🌐 Agent IP 地址: http://$EXTERNAL_IP" ''' return f'''name: CI-CD-AKS-Agent on: push: branches: ["main"] workflow_dispatch: jobs: build_and_deploy: runs-on: self-hosted env: ACR_LOGIN_SERVER: ${{{{ secrets.ACR_LOGIN_SERVER }}}} ACR_USERNAME: ${{{{ secrets.ACR_USERNAME }}}} ACR_PASSWORD: ${{{{ secrets.ACR_PASSWORD }}}} AZ_CLIENT_ID: ${{{{ secrets.AZ_CLIENT_ID }}}} AZ_CLIENT_SECRET: ${{{{ secrets.AZ_CLIENT_SECRET }}}} AZ_TENANT_ID: ${{{{ secrets.AZ_TENANT_ID }}}} AZ_SUBSCRIPTION_ID: ${{{{ secrets.AZ_SUBSCRIPTION_ID }}}} AZ_RG: ${{{{ secrets.AZ_RG }}}} AZ_AKS: ${{{{ secrets.AZ_AKS }}}} AZURE_DNS_ZONE: ${{{{ secrets.AZURE_DNS_ZONE }}}} steps: - uses: actions/checkout@v4 - name: Install Azure CLI run: | if ! command -v az &> /dev/null; then curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash fi - name: Install kubectl run: | if ! command -v kubectl &> /dev/null; then ARCH=$(uname -m) case $ARCH in x86_64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; esac curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${{ARCH}}/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl rm kubectl fi - name: Build Docker image (ARM64) run: | docker build -t $ACR_LOGIN_SERVER/{image_repo}:${{{{ github.sha }}}} . docker tag $ACR_LOGIN_SERVER/{image_repo}:${{{{ github.sha }}}} $ACR_LOGIN_SERVER/{image_repo}:latest - name: Login to ACR run: | echo "$ACR_PASSWORD" | docker login $ACR_LOGIN_SERVER -u "$ACR_USERNAME" --password-stdin - name: Push Docker image run: | docker push $ACR_LOGIN_SERVER/{image_repo}:${{{{ github.sha }}}} docker push $ACR_LOGIN_SERVER/{image_repo}:latest echo "✅ 镜像已推送: $ACR_LOGIN_SERVER/{image_repo}:latest" {deploy_step} ''' def generate_readme( self, agent_name: str, description: str, tools: List[Dict], auto_deploy: bool = True ) -> str: """生成 README.md""" tools_doc = "\n".join([f"- **{t.get('name')}**: {t.get('description', '')}" for t in tools]) k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") deploy_doc = "" if auto_deploy: deploy_doc = f''' ## 自动部署 CI/CD 会自动: 1. 构建 Docker 镜像 (ARM64) 2. 推送到 Azure Container Registry 3. 部署到 AKS 集群 4. 创建 DNS 记录 部署后可通过以下方式访问: - 命名空间: `agent-{k8s_name}` - 服务: `{k8s_name}-service` - **公网域名**: `http://{k8s_name}.taijiagnet.com` ''' return f'''# {agent_name} {description} ## 自动生成信息 - 生成时间: {datetime.now().isoformat()} - 生成方式: Agent Manager 动态生成 - 框架: agent_templates/agents/_template - 参考: [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS) ## 包含工具 {tools_doc} ## API 端点 ### REST API - `GET /` - 服务信息 - `GET /health` - 健康检查 - `GET /tools` - 列出可用工具 - `POST /tools/call` - 调用工具 ### MCP 端点 - `POST /mcp` - MCP HTTP 端点 - `GET /mcp/sse` - MCP SSE 端点 - `POST /mcp/sse` - MCP SSE POST 端点 ## 调用示例 ```bash # 健康检查 curl http://localhost:8000/health # 列出工具 curl http://localhost:8000/tools # 调用工具 curl -X POST http://localhost:8000/tools/call \\ -H "Content-Type: application/json" \\ -H "api-key: your-api-key" \\ -d '{{"tool_name": "example_tool", "parameters": {{}}}}' # MCP 调用 curl -X POST http://localhost:8000/mcp \\ -H "Content-Type: application/json" \\ -d '{{"jsonrpc": "2.0", "method": "tools/list", "id": 1}}' ``` ## 环境变量 - `API_PORT`: 服务端口 (默认: 8000) - `API_HOST`: 服务地址 (默认: 0.0.0.0) - `OPENAI_API_KEY`: AI 模型 API Key - `OPENAI_BASE_URL`: AI 模型 API 端点 - `MODEL_NAME`: AI 模型名称 - `TOOL_API_KEY`: 工具 API 密钥 (如需要) ## CI/CD 配置 本仓库配置了 Gitea Actions (self-hosted runner),推送到 main 分支后会自动: 1. 构建 Docker 镜像 (ARM64) 2. 推送到 Azure Container Registry 3. {"部署到 AKS 集群" if auto_deploy else "等待手动部署"} ### 必需的 Secrets 在 Gitee 仓库设置中配置以下 Secrets: | Secret 名称 | 说明 | 值 | |-------------|------|-----| | `ACR_LOGIN_SERVER` | ACR 登录服务器 | `agnettaiji.azurecr.io` | | `ACR_USERNAME` | ACR 用户名 | `agnettaiji` | | `ACR_PASSWORD` | ACR 密码 | *(配置为仓库Secret)* | | `AZ_CLIENT_ID` | Azure 服务主体 Client ID | `fb306798-2cfe-4ac9-ba48-eab7bc71bcfe` | | `AZ_CLIENT_SECRET` | Azure 服务主体密钥 | *(配置为仓库Secret)* | | `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` | {deploy_doc} ## 许可证 MIT License ''' def generate_full_project( self, agent_name: str, description: str, tools_config: List[Dict], auto_deploy: bool = True ) -> Dict[str, str]: """ 生成完整的 Agent 项目文件 基于 agent_templates/agents/_template 框架 Args: agent_name: Agent 名称 description: Agent 描述 tools_config: 工具配置列表 auto_deploy: 是否自动部署到 AKS Returns: 文件字典 {path: content} """ files = {} k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") # 生成 src/server/mcp_server.py files["src/server/mcp_server.py"] = self.generate_mcp_server( agent_name, description, tools_config ) # 生成 src/server/api_server.py files["src/server/api_server.py"] = self.generate_api_server(agent_name, description) # 生成 src/server/__init__.py files["src/server/__init__.py"] = '"""服务器模块"""\n' # 生成 src/__init__.py files["src/__init__.py"] = '"""源码模块"""\n' # 生成入口文件 files["run_api_server.py"] = self.generate_run_api_server() # 生成 Dockerfile files["Dockerfile"] = self.generate_dockerfile(agent_name) # 生成 requirements.txt files["requirements.txt"] = self.generate_requirements() # 生成 CI/CD 配置 files[".gitea/workflows/ci-cd.yaml"] = self.generate_gitea_action(k8s_name, auto_deploy) # 生成 README files["README.md"] = self.generate_readme(agent_name, description, tools_config, auto_deploy) return files def get_cicd_secrets_info(self) -> Dict[str, str]: """获取 CI/CD 密钥信息(用于配置仓库 Secrets)""" return self.cicd_secrets.copy() # 全局实例 agent_code_generator = AgentCodeGenerator()