diff --git a/agent_code_generator.py b/agent_code_generator.py index fecde7a..7301434 100644 --- a/agent_code_generator.py +++ b/agent_code_generator.py @@ -12,6 +12,7 @@ import re import requests from typing import Dict, List, Optional, Any from datetime import datetime +from k8s_manager import sanitize_k8s_name logger = logging.getLogger(__name__) @@ -1646,7 +1647,7 @@ class CallbackContextManager: replicas: 副本数量 tool_api_keys: 工具 API 密钥列表(将注入到容器环境变量) """ - k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") + k8s_name = sanitize_k8s_name(agent_name) image_repo = f"{self.acr_namespace}/{k8s_name}" # 生成工具 API Key 环境变量配置 @@ -1914,7 +1915,7 @@ jobs: ) -> 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(" ", "-") + k8s_name = sanitize_k8s_name(agent_name) deploy_doc = "" if auto_deploy: @@ -2053,7 +2054,7 @@ MIT License """ files = {} - k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") + k8s_name = sanitize_k8s_name(agent_name) # 生成 src/server/mcp_server.py files["src/server/mcp_server.py"] = self.generate_mcp_server( diff --git a/agent_templates/agents/a2a_litellm_agent/a2a_litellm_agent.Dockerfile b/agent_templates/agents/a2a_litellm_agent/a2a_litellm_agent.Dockerfile index f364ab0..b6b35cd 100644 --- a/agent_templates/agents/a2a_litellm_agent/a2a_litellm_agent.Dockerfile +++ b/agent_templates/agents/a2a_litellm_agent/a2a_litellm_agent.Dockerfile @@ -24,7 +24,7 @@ COPY agents/a2a_litellm_agent/*.py /app/ # 设置环境变量 ENV SERVICE_HOST=0.0.0.0 -ENV SERVICE_PORT=8080 +ENV SERVICE_PORT=8000 ENV POD_NAME=a2a-litellm-agent ENV TEMPLATE_TYPE=a2a_litellm_agent ENV PYTHONUNBUFFERED=1 @@ -34,10 +34,10 @@ ENV AGENT_CALLBACK_URL=http://mcp-server.taiji-ai.svc.cluster.local:8002/api/v1/ # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()" || exit 1 + CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()" || exit 1 # 暴露端口 -EXPOSE 8080 +EXPOSE 8000 # 启动命令 CMD ["python", "main.py"] diff --git a/agent_templates/agents/a2a_litellm_agent/a2a_server.py b/agent_templates/agents/a2a_litellm_agent/a2a_server.py index 67df7a9..9a26b48 100644 --- a/agent_templates/agents/a2a_litellm_agent/a2a_server.py +++ b/agent_templates/agents/a2a_litellm_agent/a2a_server.py @@ -26,7 +26,7 @@ logger = structlog.get_logger() # 环境变量配置 SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") -SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080")) +SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8000")) POD_NAME = os.getenv("POD_NAME", "a2a-litellm-agent") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "a2a_litellm_agent") @@ -523,12 +523,12 @@ def create_app(api_key: Optional[str] = None, model: Optional[str] = None) -> Fa 创建FastAPI应用(用于uvicorn启动) 使用方式: - uvicorn a2a_server:app --host 0.0.0.0 --port 8080 + uvicorn a2a_server:app --host 0.0.0.0 --port 8000 或设置环境变量后: export LITELLM_API_KEY="your-key" export MODEL_NAME="your-model" - uvicorn a2a_server:app --host 0.0.0.0 --port 8080 + uvicorn a2a_server:app --host 0.0.0.0 --port 8000 """ server = A2AAgentServer(api_key=api_key, model=model) return server.app diff --git a/agent_templates/agents/a2a_litellm_agent/config.py b/agent_templates/agents/a2a_litellm_agent/config.py index 877202a..ed62c6e 100644 --- a/agent_templates/agents/a2a_litellm_agent/config.py +++ b/agent_templates/agents/a2a_litellm_agent/config.py @@ -70,7 +70,7 @@ class AgentConfig: version: str = "1.0.0" # 服务端口 - port: int = 8080 + port: int = 8000 # 服务主机 host: str = "0.0.0.0" diff --git a/agent_templates/agents/a2a_litellm_agent/main.py b/agent_templates/agents/a2a_litellm_agent/main.py index 0510322..156aa90 100644 --- a/agent_templates/agents/a2a_litellm_agent/main.py +++ b/agent_templates/agents/a2a_litellm_agent/main.py @@ -8,7 +8,7 @@ from a2a_server import create_app # 环境变量配置 SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") -SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080")) +SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8000")) POD_NAME = os.getenv("POD_NAME", "a2a-litellm-agent") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "a2a_litellm_agent") diff --git a/agent_templates/agents/azure_blob_agent/azure_blob_agent.Dockerfile b/agent_templates/agents/azure_blob_agent/azure_blob_agent.Dockerfile index 36fa495..b155b4f 100644 --- a/agent_templates/agents/azure_blob_agent/azure_blob_agent.Dockerfile +++ b/agent_templates/agents/azure_blob_agent/azure_blob_agent.Dockerfile @@ -26,11 +26,11 @@ COPY common/api_key_utils.py /app/common/ # 设置环境变量 ENV PYTHONUNBUFFERED=1 ENV SERVICE_HOST=0.0.0.0 -ENV SERVICE_PORT=8080 +ENV SERVICE_PORT=8000 # 健康检查 - 使用Python避免僵尸进程 HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()" || exit 1 + CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()" || exit 1 # 运行agent (直接使用Python,避免shell) CMD ["python3", "-u", "azure_blob_agent.py"] diff --git a/agent_templates/agents/azure_blob_agent/azure_blob_agent.py b/agent_templates/agents/azure_blob_agent/azure_blob_agent.py index 8efdc0f..0649c23 100644 --- a/agent_templates/agents/azure_blob_agent/azure_blob_agent.py +++ b/agent_templates/agents/azure_blob_agent/azure_blob_agent.py @@ -25,7 +25,7 @@ logger = logging.getLogger(__name__) # 环境变量配置 SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") -SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080")) +SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8000")) POD_NAME = os.getenv("POD_NAME", "azure-blob-agent") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent") diff --git a/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.Dockerfile b/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.Dockerfile index 5193672..0d28a9b 100644 --- a/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.Dockerfile +++ b/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.Dockerfile @@ -19,11 +19,11 @@ COPY agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py /app/ COPY common/api_key_utils.py /app/common/ # 暴露端口 -EXPOSE 8080 +EXPOSE 8000 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import requests; requests.get('http://localhost:8080/health', timeout=5)" + CMD python -c "import requests; requests.get('http://localhost:8000/health', timeout=5)" # 启动应用 CMD ["python", "azure_blob_agent_a2a.py"] diff --git a/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py b/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py index 6343c5c..6137047 100644 --- a/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py +++ b/agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) # 环境变量配置 SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") -SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080")) +SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8000")) POD_NAME = os.getenv("POD_NAME", "azure-blob-agent-a2a") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_a2a") AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "a2a") diff --git a/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.Dockerfile b/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.Dockerfile index 5f72baa..d17b573 100644 --- a/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.Dockerfile +++ b/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.Dockerfile @@ -18,11 +18,11 @@ RUN pip install --no-cache-dir -r requirements_mcp.txt COPY agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py /app/ # 暴露端口 -EXPOSE 8080 +EXPOSE 8000 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import requests; requests.get('http://localhost:8080/health', timeout=5)" + CMD python -c "import requests; requests.get('http://localhost:8000/health', timeout=5)" # 启动应用 CMD ["python", "azure_blob_agent_mcp.py"] diff --git a/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py b/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py index 06b98fa..c7482a4 100644 --- a/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py +++ b/agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py @@ -22,7 +22,7 @@ logger = logging.getLogger(__name__) # 环境变量配置 SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") -SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080")) +SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8000")) POD_NAME = os.getenv("POD_NAME", "azure-blob-agent-mcp") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_mcp") AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "mcp") diff --git a/agent_templates/agents/jina_search_agent/jina_search_agent.Dockerfile b/agent_templates/agents/jina_search_agent/jina_search_agent.Dockerfile index 0bb36a8..dba1553 100644 --- a/agent_templates/agents/jina_search_agent/jina_search_agent.Dockerfile +++ b/agent_templates/agents/jina_search_agent/jina_search_agent.Dockerfile @@ -25,7 +25,7 @@ COPY agents/jina_search_agent/jina_search_agent.py /app/ # 环境变量 ENV PYTHONUNBUFFERED=1 ENV SERVICE_HOST=0.0.0.0 -ENV SERVICE_PORT=8080 +ENV SERVICE_PORT=8000 # 默认 Jina API Key (硬编码) ENV JINA_API_KEY=jina_e26dc30420a44a1e859216528065b203TkMRmsoz-FgMDQC5FZX9jr5oF2CI @@ -35,8 +35,8 @@ ENV AGENT_CALLBACK_URL=http://mcp-server:8002/api/v1/billing/agent-callback # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \ - CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()" || exit 1 + CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()" || exit 1 -EXPOSE 8080 +EXPOSE 8000 CMD ["python3", "-u", "jina_search_agent.py"] diff --git a/agent_templates/agents/jina_search_agent/jina_search_agent.py b/agent_templates/agents/jina_search_agent/jina_search_agent.py index 0596641..3db93c0 100644 --- a/agent_templates/agents/jina_search_agent/jina_search_agent.py +++ b/agent_templates/agents/jina_search_agent/jina_search_agent.py @@ -31,7 +31,7 @@ logger = logging.getLogger(__name__) # 环境变量 SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") -SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080")) +SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8000")) POD_NAME = os.getenv("POD_NAME", "jina-search-agent") USER_ID = os.getenv("USER_ID", "") JINA_API_KEY = os.getenv("JINA_API_KEY", "") diff --git a/app.py b/app.py index 9db29dc..0ffeb4f 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,7 @@ from sqlalchemy.orm import Session from datetime import datetime import logging -from k8s_manager import K8sManager +from k8s_manager import K8sManager, sanitize_k8s_name from database import ( get_db, Template, Agent, Quota, AgentMetric, AgentType, AgentStatus, parse_resource_string @@ -328,6 +328,12 @@ async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db detail=f"无效的框架类型。支持的框架: {', '.join(valid_frameworks)}" ) + # DNS-1035 名称合规化:确保名称可以用作 K8s 资源名称 + original_name = request.name + request.name = sanitize_k8s_name(request.name) + if original_name != request.name: + logger.info(f"🔄 Agent 名称已合规化: '{original_name}' -> '{request.name}' (DNS-1035)") + # 合并环境变量到config config_data = request.config.copy() config_data["agent_framework"] = framework # 添加框架类型到配置 @@ -587,8 +593,11 @@ async def delete_agent(agent_name: str, db: Session = Depends(get_db)): try: logger.info(f"收到删除Agent请求: {agent_name}") + # DNS-1035 名称合规化 + agent_name = sanitize_k8s_name(agent_name) + # 保护机制:防止删除 agent-manager 命名空间 - computed_namespace = f"agent-{agent_name}".lower().strip('-')[:63] + computed_namespace = f"agent-{agent_name}"[:63].rstrip('-') if computed_namespace == "agent-manager": logger.error(f"❌ 禁止删除 agent-manager 命名空间!agent_name={agent_name}, computed_namespace={computed_namespace}") raise HTTPException( diff --git a/docs/port-unify-8000-change-plan.md b/docs/port-unify-8000-change-plan.md new file mode 100644 index 0000000..32bf6a3 --- /dev/null +++ b/docs/port-unify-8000-change-plan.md @@ -0,0 +1,125 @@ +# Agent 端口统一为 8000 改动方案(除 search_agent 外) + +目标:除 **search_agent / search_agent_a2a / search_agent_mcp** 外,所有 agent 统一使用端口 **8000**。 + +--- + +## 一、当前状态汇总 + +| 模板名 | template_manager port | k8s TEMPLATE_PORTS | k8s container_port | 代码/Dockerfile 默认 | 需改动 | +|--------|------------------------|--------------------|---------------------|-----------------------|--------| +| search_agent | 8000 | 8080 | 8080 | 8080 | 否(保持 8080) | +| search_agent_a2a | 8000 | 8080 | 8080 | 8080 | 否(保持 8080) | +| search_agent_mcp | 8000 | 8080 | 8080 | 8080 | 否(保持 8080) | +| jina_search_agent | 8080 | 8080 | 8080 | 8080 | **是 → 8000** | +| azure_blob_agent | 8080 | 8080 | 8080 | 8080 | **是 → 8000** | +| azure_blob_agent_mcp | 8080 | 8080 | 8080 | 8080 | **是 → 8000** | +| azure_blob_agent_a2a | 8080 | 8080 | 8080 | 8080 | **是 → 8000** | +| a2a_litellm_agent | 8080 | 8080 | 8080 | 8080 | **是 → 8000** | +| echo_agent / mysql_agent / postgresql_agent / code_ai_agent / facebook_agent / media_downloader / content_analyzer / huoke / microsoft_learn_agent / aws_docs_mcp / google_mcp | 8000 | 8000 | 8000 或未显式 | 8000 | 否 | + +说明:search_agent 系列在 template_manager 中为 8000 与容器实际 8080 不一致,若需与现状一致可单独将 template_manager 中 search_agent* 改为 8080(本次方案不包含,仅统一「非 search_agent」为 8000)。 + +--- + +## 二、需改动的 5 个 Agent + +1. **jina_search_agent** +2. **azure_blob_agent** +3. **azure_blob_agent_mcp** +4. **azure_blob_agent_a2a** +5. **a2a_litellm_agent** + +--- + +## 三、具体改动清单 + +### 1. template_manager.py + +- **jina_search_agent**:`"port": 8080` → `"port": 8000` +- **azure_blob_agent**:`"port": 8080` → `"port": 8000` +- **azure_blob_agent_mcp**:`"port": 8080` → `"port": 8000` +- **azure_blob_agent_a2a**:`"port": 8080` → `"port": 8000` +- **a2a_litellm_agent**:`"port": 8080` → `"port": 8000` + +### 2. k8s_manager.py + +- **TEMPLATE_PORTS**:上述 5 个模板的端口由 `8080` 改为 `8000`(search_agent / search_agent_a2a / search_agent_mcp 保持 8080)。 +- **container_ports**(约 746–750 行): + - 当前 8080 列表:`jina_search_agent, azure_blob_agent, azure_blob_agent_mcp, azure_blob_agent_a2a, search_agent, search_agent_a2a, search_agent_mcp, a2a_litellm_agent` + - 修改为:仅 **search_agent, search_agent_a2a, search_agent_mcp** 使用 `container_port=8080`;**jina_search_agent, azure_blob_agent, azure_blob_agent_mcp, azure_blob_agent_a2a, a2a_litellm_agent** 移到 8000 分支,使用 `container_port=8000`。 +- **TEMPLATE_ENV_INFO** 中「SERVICE_PORT」说明: + - **jina_search_agent**:`"默认8080"` → `"默认8000"` + - **azure_blob_agent**:`"默认8080"` → `"默认8000"` + - **azure_blob_agent_mcp**:无 SERVICE_PORT 时可补充 `"SERVICE_PORT": "HTTP服务端口,默认8000"`(若有则改为 8000) + - **azure_blob_agent_a2a**:同上 + - **a2a_litellm_agent**:`"默认8080"` → `"默认8000"` + +### 3. Agent 代码与 Dockerfile(每个 agent 内默认端口 8080 → 8000) + +#### 3.1 jina_search_agent + +| 文件 | 改动 | +|------|------| +| `agent_templates/agents/jina_search_agent/jina_search_agent.py` | `SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))` → `"8000"` | +| `agent_templates/agents/jina_search_agent/jina_search_agent.Dockerfile` | `ENV SERVICE_PORT=8080` → `8000`;健康检查与 `EXPOSE` 中 `8080` → `8000` | + +#### 3.2 azure_blob_agent + +| 文件 | 改动 | +|------|------| +| `agent_templates/agents/azure_blob_agent/azure_blob_agent.py` | `SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))` → `"8000"` | +| `agent_templates/agents/azure_blob_agent/azure_blob_agent.Dockerfile` | `ENV SERVICE_PORT=8080` → `8000`;健康检查 URL 中 `8080` → `8000` | + +#### 3.3 azure_blob_agent_mcp + +| 文件 | 改动 | +|------|------| +| `agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.py` | `SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))` → `"8000"` | +| `agent_templates/agents/azure_blob_agent_mcp/azure_blob_agent_mcp.Dockerfile` | `EXPOSE 8080` → `8000`;健康检查 URL 中 `8080` → `8000` | + +#### 3.4 azure_blob_agent_a2a + +| 文件 | 改动 | +|------|------| +| `agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py` | `SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))` → `"8000"` | +| `agent_templates/agents/azure_blob_agent_a2a/azure_blob_agent_a2a.Dockerfile` | `EXPOSE 8080` → `8000`;健康检查 URL 中 `8080` → `8000` | + +#### 3.5 a2a_litellm_agent + +| 文件 | 改动 | +|------|------| +| `agent_templates/agents/a2a_litellm_agent/main.py` | `SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))` → `"8000"` | +| `agent_templates/agents/a2a_litellm_agent/config.py` | `port: int = 8080` → `port: int = 8000` | +| `agent_templates/agents/a2a_litellm_agent/a2a_server.py` | `SERVICE_PORT = int(os.getenv("SERVICE_PORT", "8080"))` → `"8000"`;文档/示例中的 `--port 8080` → `--port 8000` | +| `agent_templates/agents/a2a_litellm_agent/a2a_litellm_agent.Dockerfile` | `ENV SERVICE_PORT=8080` → `8000`;健康检查与 `EXPOSE` 中 `8080` → `8000` | + +--- + +## 四、未在 Agent Manager 默认模板中的 Agent(可选统一) + +以下 agent 在仓库中存在且默认使用 8080,但未出现在 `template_manager.DEFAULT_TEMPLATES` / `k8s_manager.TEMPLATE_PORTS` 中。若希望「全仓库除 search_agent 外一律 8000」,可一并改: + +- **chain_explorer_agent**:`chain_explorer_agent.py` 默认 8080 → 8000;Dockerfile 已是 8000,无需改。 +- **chain_analysis_agent**:同上(代码 8080 → 8000,Dockerfile 已是 8000)。 +- **stock_analysis_agent**:`stock_analysis_agent.py` 默认 8080 → 8000;Dockerfile 中 8080 → 8000。 +- **stock_news_agent**:同上。 +- **stock_quote_agent**:同上。 + +若这些模板后续加入 Agent Manager,建议直接以 8000 登记。 + +--- + +## 五、实施顺序建议 + +1. 改 **template_manager.py**、**k8s_manager.py**(端口与 K8s 一致)。 +2. 改上述 5 个 agent 的 **代码 + Dockerfile**。 +3. 重新构建并推送对应镜像;已运行中的 Pod 需用新镜像重启或重新部署。 +4. 若数据库已初始化过模板,需将上述 5 个模板的 `port` 字段更新为 8000(或重新从默认模板初始化)。 + +--- + +## 六、改动后端口约定(总结) + +- **8000**:除 search_agent 系列外的所有 agent(含 jina_search_agent、azure_blob_agent 系列、a2a_litellm_agent、echo/mysql/postgresql/code_ai/facebook 等)。 +- **8080**:仅 **search_agent**、**search_agent_a2a**、**search_agent_mcp**。 diff --git a/external_tool_api.py b/external_tool_api.py index e9c7893..2afc9ff 100644 --- a/external_tool_api.py +++ b/external_tool_api.py @@ -21,6 +21,7 @@ from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel, Field from agent_code_generator import agent_code_generator +from k8s_manager import sanitize_k8s_name from tool_storage import tool_storage from gitee_manager import gitee_manager @@ -860,10 +861,10 @@ async def create_agent_with_tools(request: CreateAgentWithToolsRequest): # 生成唯一后缀,确保不同用户创建同名 Agent 不会冲突 unique_suffix = uuid.uuid4().hex[:6] - base_name = request.name.lower().replace("_", "-").replace(" ", "-") + base_name = sanitize_k8s_name(request.name) - # k8s_name 带唯一后缀,避免域名/namespace 冲突 - k8s_name = f"{base_name}-{unique_suffix}" + # k8s_name 带唯一后缀,避免域名/namespace 冲突(确保 DNS-1035 合规) + k8s_name = sanitize_k8s_name(f"{base_name}-{unique_suffix}") repo_name = f"agent-{k8s_name}" agent_ref_id = f"agent-{repo_name}" @@ -1102,12 +1103,12 @@ async def get_agent_build_status(agent_ref_id: str): if agent_info and agent_info.get("k8s_name"): k8s_name = agent_info.get("k8s_name") else: - # 无法从 repo_name 准确推断,使用 agent 名称 + # 无法从 repo_name 准确推断,使用 agent 名称(确保 DNS-1035 合规) agent_name = agent_info.get("name") if agent_info else None if agent_name: - k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") + k8s_name = sanitize_k8s_name(agent_name) else: - k8s_name = repo_name.lower().replace("_", "-").replace(" ", "-") + k8s_name = sanitize_k8s_name(repo_name) expected_domain = f"{k8s_name}.taijiagnet.com" namespace = f"agent-{k8s_name}" @@ -1172,7 +1173,7 @@ async def get_agent_deployment_info(agent_ref_id: str): repo_name = agent_ref_id.replace("agent-", "", 1) else: repo_name = agent_ref_id - k8s_name = repo_name.lower().replace("_", "-").replace(" ", "-") + k8s_name = sanitize_k8s_name(repo_name) namespace = f"agent-{k8s_name}" # 导入 K8sManager 查询实际状态 diff --git a/k8s_manager.py b/k8s_manager.py index 5f77f31..728f8a9 100644 --- a/k8s_manager.py +++ b/k8s_manager.py @@ -6,6 +6,7 @@ from kubernetes.client.rest import ApiException from typing import Dict, List, Optional import logging import os +import re import requests import time @@ -13,6 +14,45 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +def sanitize_k8s_name(name: str, max_length: int = 63) -> str: + """将名称转换为 DNS-1035 合规格式 + + Kubernetes Service、Deployment 等资源名称必须符合 DNS-1035 标准: + - 只能包含小写字母、数字和连字符 '-' + - 必须以字母开头 + - 必须以字母或数字结尾 + - 最长 63 个字符 + + Args: + name: 原始名称 + max_length: 最大长度(默认 63) + + Returns: + 合规的 K8s 资源名称 + """ + # 转小写 + name = name.lower() + # 将下划线和空格替换为连字符 + name = name.replace("_", "-").replace(" ", "-") + # 移除非字母、数字、连字符的字符 + name = re.sub(r'[^a-z0-9-]', '', name) + # 合并连续的连字符 + name = re.sub(r'-+', '-', name) + # 如果以数字开头,添加 'a' 前缀 + if name and name[0].isdigit(): + name = 'a' + name + # 如果以连字符开头,去掉 + name = name.lstrip('-') + # 截断到最大长度 + name = name[:max_length] + # 去掉末尾的连字符 + name = name.rstrip('-') + # 最终兜底:如果名称为空 + if not name: + name = 'agent' + return name + + class K8sManager: """Kubernetes资源管理器""" @@ -94,11 +134,9 @@ class K8sManager: Returns: 创建的命名空间名称 """ - # 生成命名空间名称(使用 agent-{agent_name} 格式) - namespace_name = f"agent-{agent_name}" - - # 确保命名空间名称符合 DNS 标准(最多 63 个字符,只能包含小写字母、数字和连字符) - namespace_name = namespace_name[:63].lower().strip('-') + # 生成命名空间名称(使用 agent-{sanitized_name} 格式,确保 DNS 合规) + sanitized_name = sanitize_k8s_name(agent_name) + namespace_name = f"agent-{sanitized_name}"[:63].rstrip('-') try: # 检查命名空间是否已存在 @@ -318,11 +356,11 @@ class K8sManager: "search_agent_mcp": 8080, "mysql_agent": 8000, "postgresql_agent": 8000, - "jina_search_agent": 8080, - "azure_blob_agent": 8080, - "azure_blob_agent_mcp": 8080, - "azure_blob_agent_a2a": 8080, - "a2a_litellm_agent": 8080, + "jina_search_agent": 8000, + "azure_blob_agent": 8000, + "azure_blob_agent_mcp": 8000, + "azure_blob_agent_a2a": 8000, + "a2a_litellm_agent": 8000, "code_ai_agent": 8000, "facebook_agent": 8000, "media_downloader": 8000, @@ -340,7 +378,7 @@ class K8sManager: "JINA_API_KEY": "Jina API密钥,从 https://jina.ai/ 获取" }, "optional": { - "SERVICE_PORT": "HTTP服务端口,默认8080", + "SERVICE_PORT": "HTTP服务端口,默认8000", "SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0" } }, @@ -376,7 +414,7 @@ class K8sManager: }, "optional": { "AZURE_STORAGE_CONNECTION_STRING": "Azure Storage连接字符串(可选,也可通过 /connect API 动态传入)", - "SERVICE_PORT": "HTTP服务端口,默认8080", + "SERVICE_PORT": "HTTP服务端口,默认8000", "SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0" } }, @@ -479,7 +517,7 @@ class K8sManager: "LITELLM_API_KEY": "LiteLLM API 密钥(可在请求中传入)", "AGENT_NAME": "Agent 名称", "AGENT_DESCRIPTION": "Agent 描述", - "SERVICE_PORT": "HTTP服务端口,默认 8080", + "SERVICE_PORT": "HTTP服务端口,默认 8000", "SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0" } }, @@ -706,9 +744,9 @@ class K8sManager: # 设置容器端口(如果是HTTP服务类型的agent) container_ports = None - if template in ["jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "search_agent", "search_agent_a2a", "search_agent_mcp", "a2a_litellm_agent"]: + if template in ["search_agent", "search_agent_a2a", "search_agent_mcp"]: container_ports = [client.V1ContainerPort(container_port=8080)] - elif template in ["code_ai_agent", "facebook_agent", "echo_agent", "mysql_agent", "postgresql_agent"]: + elif template in ["jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent", "code_ai_agent", "facebook_agent", "echo_agent", "mysql_agent", "postgresql_agent"]: container_ports = [client.V1ContainerPort(container_port=8000)] # 创建Pod规格 @@ -785,7 +823,8 @@ class K8sManager: Returns: 删除结果 """ - namespace_name = f"agent-{agent_name}".lower().strip('-')[:63] + sanitized_name = sanitize_k8s_name(agent_name) + namespace_name = f"agent-{sanitized_name}"[:63].rstrip('-') # region agent log try: import json, time diff --git a/scripts/test_create_delete_agent.sh b/scripts/test_create_delete_agent.sh new file mode 100755 index 0000000..899c4fe --- /dev/null +++ b/scripts/test_create_delete_agent.sh @@ -0,0 +1,107 @@ +#!/bin/bash +# 创建 Agent 测试是否正常工作,然后删除 +# 使用: AGENT_MANAGER_URL=http://localhost:8000 ./scripts/test_create_delete_agent.sh +# 或: ./scripts/test_create_delete_agent.sh http://your-manager:8000 + +set -e +BASE_URL="${1:-${AGENT_MANAGER_URL:-http://localhost:8000}}" +AGENT_NAME="test-echo-$(date +%s)" + +echo "==========================================" +echo "Agent 创建/删除测试" +echo "==========================================" +echo "Manager URL: $BASE_URL" +echo "Agent 名称: $AGENT_NAME" +echo "" + +# 1. 创建 Agent (echo_agent 无需额外 config) +echo ">>> 1. 创建 Agent (template=echo_agent)..." +CREATE_RESP=$(curl -s -w "\n%{http_code}" -X POST "${BASE_URL}/agents" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"${AGENT_NAME}\", + \"template\": \"echo_agent\", + \"config\": {} + }") +HTTP_BODY=$(echo "$CREATE_RESP" | head -n -1) +HTTP_CODE=$(echo "$CREATE_RESP" | tail -n 1) + +if [ "$HTTP_CODE" != "200" ]; then + echo "创建失败 HTTP $HTTP_CODE" + echo "$HTTP_BODY" | python3 -m json.tool 2>/dev/null || echo "$HTTP_BODY" + exit 1 +fi + +echo "创建成功" +echo "$HTTP_BODY" | python3 -m json.tool 2>/dev/null || echo "$HTTP_BODY" +echo "" + +# 从响应中取 pod_ip 或 access_info +POD_IP=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('pod_ip','') or (d.get('access_info',{}) or {}).get('pod_url','').split('//')[-1].split(':')[0])" 2>/dev/null || true) +EXTERNAL_IP=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); a=d.get('access_info',{}); print(a.get('external_ip','') or a.get('ip_url','').split('//')[-1].split(':')[0] if isinstance(a,dict) else '')" 2>/dev/null || true) +SERVICE_PORT=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('service_port', 8000) or 8000)" 2>/dev/null || echo "8000") + +# 2. 等待 Pod 就绪并测活 +echo ">>> 2. 等待 Pod 就绪并测试健康..." +for i in 1 2 3 4 5 6 7 8 9 10; do + STATUS_RESP=$(curl -s -w "\n%{http_code}" "${BASE_URL}/agents/${AGENT_NAME}/status") + STATUS_BODY=$(echo "$STATUS_RESP" | head -n -1) + STATUS_CODE=$(echo "$STATUS_RESP" | tail -n 1) + if [ "$STATUS_CODE" != "200" ]; then + sleep 3 + continue + fi + STATUS=$(echo "$STATUS_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status',''))" 2>/dev/null || true) + if [ "$STATUS" = "Running" ]; then + break + fi + sleep 3 +done + +if [ "$STATUS" != "Running" ]; then + echo "Pod 未在预期内变为 Running,当前 status: $STATUS" + echo "继续尝试访问 Agent 健康端点..." +fi + +# 尝试访问 Agent:优先外网 IP:80,否则 pod_ip:service_port +AGENT_URL="" +if [ -n "$EXTERNAL_IP" ]; then + AGENT_URL="http://${EXTERNAL_IP}:80" +elif [ -n "$POD_IP" ]; then + AGENT_URL="http://${POD_IP}:${SERVICE_PORT}" +fi + +if [ -n "$AGENT_URL" ]; then + echo "访问 Agent: $AGENT_URL/health" + if curl -sf --connect-timeout 10 "${AGENT_URL}/health" > /dev/null; then + echo "Agent 健康检查通过" + else + echo "健康检查失败(可能 LoadBalancer 未就绪或网络不可达)" + fi + # 尝试根路径 + if curl -sf --connect-timeout 5 "${AGENT_URL}/" > /dev/null; then + echo "Agent 根路径可访问" + fi +else + echo "未获取到 Pod IP 或外网 IP,跳过 Agent 端点测试" +fi + +echo "" +echo ">>> 3. 删除 Agent..." +DEL_RESP=$(curl -s -w "\n%{http_code}" -X DELETE "${BASE_URL}/agents/${AGENT_NAME}") +DEL_BODY=$(echo "$DEL_RESP" | head -n -1) +DEL_CODE=$(echo "$DEL_RESP" | tail -n 1) + +if [ "$DEL_CODE" = "200" ] || [ "$DEL_CODE" = "204" ]; then + echo "删除成功" + echo "$DEL_BODY" | python3 -m json.tool 2>/dev/null || echo "$DEL_BODY" +else + echo "删除返回 HTTP $DEL_CODE" + echo "$DEL_BODY" + exit 1 +fi + +echo "" +echo "==========================================" +echo "测试完成: 创建 -> 检查状态 -> 删除 均成功" +echo "==========================================" diff --git a/scripts/test_search_agent.sh b/scripts/test_search_agent.sh new file mode 100755 index 0000000..de57171 --- /dev/null +++ b/scripts/test_search_agent.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# 创建 Search Agent、测试健康与状态、可选调用 /search、然后删除 +# 使用: AGENT_MANAGER_URL=http://20.212.121.126 ./scripts/test_search_agent.sh +# 可选环境变量: LLM_BASE_URL, SERPER_API_KEY, JINA_API_KEY (不设则用默认/镜像内建) + +set -e +BASE_URL="${1:-${AGENT_MANAGER_URL:-http://localhost:8000}}" +AGENT_NAME="test-search-$(date +%s)" + +# 默认 LLM 地址(与 code_ai_agent 等一致),可通过环境变量覆盖 +LLM_BASE_URL="${LLM_BASE_URL:-https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1}" +SERPER_API_KEY="${SERPER_API_KEY:-8253b4f240b520194065312f90e85f9be0fa205f}" +JINA_API_KEY="${JINA_API_KEY:-jina_e26dc30420a44a1e859216528065b203TkMRmsoz-FgMDQC5FZX9jr5oF2CI}" + +echo "==========================================" +echo "Search Agent 测试" +echo "==========================================" +echo "Manager URL: $BASE_URL" +echo "Agent 名称: $AGENT_NAME" +echo "LLM_BASE_URL: $LLM_BASE_URL" +echo "" + +# 1. 创建 Search Agent(传入 env 以通过 K8s 注入) +echo ">>> 1. 创建 Search Agent (template=search_agent)..." +CREATE_RESP=$(curl -s -w "\n%{http_code}" -X POST "${BASE_URL}/agents" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"${AGENT_NAME}\", + \"template\": \"search_agent\", + \"framework\": \"API\", + \"config\": { + \"env\": { + \"LLM_BASE_URL\": \"${LLM_BASE_URL}\", + \"SERPER_API_KEY\": \"${SERPER_API_KEY}\", + \"JINA_API_KEY\": \"${JINA_API_KEY}\" + } + } + }") +HTTP_BODY=$(echo "$CREATE_RESP" | head -n -1) +HTTP_CODE=$(echo "$CREATE_RESP" | tail -n 1) + +if [ "$HTTP_CODE" != "200" ]; then + echo "创建失败 HTTP $HTTP_CODE" + echo "$HTTP_BODY" | python3 -m json.tool 2>/dev/null || echo "$HTTP_BODY" + exit 1 +fi + +echo "创建成功" +echo "$HTTP_BODY" | python3 -m json.tool 2>/dev/null || echo "$HTTP_BODY" +echo "" + +POD_IP=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('pod_ip','') or (d.get('access_info',{}) or {}).get('pod_url','').split('//')[-1].split(':')[0])" 2>/dev/null || true) +EXTERNAL_IP=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); a=d.get('access_info',{}); print(a.get('external_ip','') or (a.get('ip_url') or '').split('//')[-1].split(':')[0] if isinstance(a,dict) else '')" 2>/dev/null || true) +SERVICE_PORT=$(echo "$HTTP_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('service_port', 8080) or 8080)" 2>/dev/null || echo "8080") + +# 2. 等待 Pod 就绪 +echo ">>> 2. 等待 Pod 就绪..." +for i in 1 2 3 4 5 6 7 8 9 10 11 12; do + STATUS_RESP=$(curl -s -w "\n%{http_code}" "${BASE_URL}/agents/${AGENT_NAME}/status") + STATUS_BODY=$(echo "$STATUS_RESP" | head -n -1) + STATUS_CODE=$(echo "$STATUS_RESP" | tail -n 1) + if [ "$STATUS_CODE" != "200" ]; then + sleep 5 + continue + fi + STATUS=$(echo "$STATUS_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status',''))" 2>/dev/null || true) + if [ "$STATUS" = "Running" ]; then + echo "Pod 状态: Running" + break + fi + echo " 等待中... status=$STATUS (${i}/12)" + sleep 5 +done + +if [ "$STATUS" != "Running" ]; then + echo "Pod 未在预期内变为 Running,当前: $STATUS" +fi + +# 3. 测试 Agent 端点(Search Agent 端口 8080,经 Service 暴露为 80) +AGENT_URL="" +if [ -n "$EXTERNAL_IP" ]; then + AGENT_URL="http://${EXTERNAL_IP}:80" +elif [ -n "$POD_IP" ]; then + AGENT_URL="http://${POD_IP}:${SERVICE_PORT}" +fi + +if [ -n "$AGENT_URL" ]; then + echo "" + echo ">>> 3. 测试 Search Agent 端点..." + echo " Base URL: $AGENT_URL" + + if curl -sf --connect-timeout 15 "${AGENT_URL}/health" > /tmp/search_health.json 2>/dev/null; then + echo " GET /health: 成功" + cat /tmp/search_health.json | python3 -m json.tool 2>/dev/null || cat /tmp/search_health.json + else + echo " GET /health: 失败或超时" + fi + + if curl -sf --connect-timeout 10 "${AGENT_URL}/status" > /tmp/search_status.json 2>/dev/null; then + echo " GET /status: 成功" + cat /tmp/search_status.json | python3 -m json.tool 2>/dev/null || cat /tmp/search_status.json + else + echo " GET /status: 失败或超时" + fi + + if curl -sf --connect-timeout 5 "${AGENT_URL}/" > /dev/null; then + echo " GET /: 成功" + fi + + # 可选:调用 /search(需要有效 llm_api_key,否则可能 400/500) + if [ -n "${LLM_API_KEY_FOR_TEST}" ]; then + echo " 调用 POST /search (简短查询)..." + SEARCH_RESP=$(curl -s -w "\n%{http_code}" -X POST "${AGENT_URL}/search" \ + -H "Content-Type: application/json" \ + -d "{\"query\": \"What is 2+2?\", \"llm_api_key\": \"${LLM_API_KEY_FOR_TEST}\"}") + SEARCH_CODE=$(echo "$SEARCH_RESP" | tail -n 1) + if [ "$SEARCH_CODE" = "200" ]; then + echo " POST /search: 成功 (HTTP 200)" + else + echo " POST /search: HTTP $SEARCH_CODE" + fi + else + echo " (跳过 /search:设置 LLM_API_KEY_FOR_TEST 可测试搜索)" + fi +else + echo "未获取到 Agent 地址,跳过端点测试" +fi + +# 4. 删除 Agent +echo "" +echo ">>> 4. 删除 Agent..." +DEL_RESP=$(curl -s -w "\n%{http_code}" -X DELETE "${BASE_URL}/agents/${AGENT_NAME}") +DEL_BODY=$(echo "$DEL_RESP" | head -n -1) +DEL_CODE=$(echo "$DEL_RESP" | tail -n 1) + +if [ "$DEL_CODE" = "200" ] || [ "$DEL_CODE" = "204" ]; then + echo "删除成功" + echo "$DEL_BODY" | python3 -m json.tool 2>/dev/null || echo "$DEL_BODY" +else + echo "删除返回 HTTP $DEL_CODE" + echo "$DEL_BODY" + exit 1 +fi + +echo "" +echo "==========================================" +echo "Search Agent 测试完成" +echo "==========================================" diff --git a/scripts/update_template_ports.py b/scripts/update_template_ports.py new file mode 100755 index 0000000..90ae110 --- /dev/null +++ b/scripts/update_template_ports.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +更新数据库中模板的端口。 + +将 search_agent / search_agent_a2a / search_agent_mcp 的 port 改为 8080, +与容器实际监听端口一致,使 Service target_port 正确。 + +用法: + # 使用项目 database 配置,仅修改 search_agent* 为 8080 + python scripts/update_template_ports.py + + # 指定要改的模板和端口 + python scripts/update_template_ports.py --names search_agent,search_agent_a2a,search_agent_mcp --port 8080 + + # 仅打印当前端口,不修改(dry-run) + python scripts/update_template_ports.py --dry-run +""" + +import os +import sys +import argparse + +# 确保项目根在 path 中 +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from database import SessionLocal, Template + + +# 默认:需要改为 8080 的模板(与 search_agent 镜像一致) +DEFAULT_TEMPLATES_8080 = ["search_agent", "search_agent_a2a", "search_agent_mcp"] + + +def get_current_ports(db, names): + """返回 {name: port}""" + rows = db.query(Template).filter(Template.name.in_(names)).all() + return {r.name: r.port for r in rows} + + +def update_ports(names: list, port: int, dry_run: bool = False): + db = SessionLocal() + try: + current = get_current_ports(db, names) + missing = [n for n in names if n not in current] + if missing: + print(f"未找到模板: {missing}") + names = [n for n in names if n in current] + if not names: + return False + + print("当前端口:") + for n in names: + print(f" {n}: {current.get(n)}") + + if dry_run: + print("\n[DRY-RUN] 未执行修改。去掉 --dry-run 将执行更新。") + return True + + updated = 0 + for name in names: + row = db.query(Template).filter(Template.name == name).first() + if row is not None and row.port != port: + row.port = port + updated += 1 + print(f" 更新 {name} -> port={port}") + + if updated: + db.commit() + print(f"\n已提交: {updated} 条记录 port 已改为 {port}") + else: + print("\n无需更新(端口已是目标值)") + + # 再次查询确认 + after = get_current_ports(db, names) + print("更新后端口:") + for n in names: + print(f" {n}: {after.get(n)}") + return True + except Exception as e: + db.rollback() + print(f"错误: {e}", file=sys.stderr) + return False + finally: + db.close() + + +def main(): + parser = argparse.ArgumentParser(description="更新模板端口") + parser.add_argument( + "--names", + type=str, + default=",".join(DEFAULT_TEMPLATES_8080), + help="模板名称,逗号分隔,默认: search_agent,search_agent_a2a,search_agent_mcp", + ) + parser.add_argument( + "--port", + type=int, + default=8080, + help="目标端口,默认 8080", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="仅打印当前端口,不修改", + ) + args = parser.parse_args() + names = [n.strip() for n in args.names.split(",") if n.strip()] + if not names: + print("请至少指定一个模板名 (--names)") + sys.exit(1) + + ok = update_ports(names, args.port, dry_run=args.dry_run) + sys.exit(0 if ok else 1) + + +if __name__ == "__main__": + main() diff --git a/template_manager.py b/template_manager.py index 5e305ba..34d1450 100644 --- a/template_manager.py +++ b/template_manager.py @@ -28,7 +28,7 @@ DEFAULT_TEMPLATES = { "display_name": "Search Agent", "description": "搜索 Agent (LangChain)", "image": "agnettaiji.azurecr.io/ai-agents/search-agent:latest", - "port": 8000, + "port": 8080, "agent_framework": "langchain", "env_requirements": {}, }, @@ -36,7 +36,7 @@ DEFAULT_TEMPLATES = { "display_name": "Search Agent A2A", "description": "搜索 Agent (A2A 协议)", "image": "agnettaiji.azurecr.io/ai-agents/search-agent-a2a:latest", - "port": 8000, + "port": 8080, "agent_framework": "a2a", "env_requirements": {}, }, @@ -44,7 +44,7 @@ DEFAULT_TEMPLATES = { "display_name": "Search Agent MCP", "description": "搜索 Agent (MCP 协议)", "image": "agnettaiji.azurecr.io/ai-agents/search-agent-mcp:latest", - "port": 8000, + "port": 8080, "agent_framework": "mcp", "env_requirements": {}, }, @@ -82,7 +82,7 @@ DEFAULT_TEMPLATES = { "display_name": "Jina Search Agent", "description": "Jina AI 搜索 Agent", "image": "agnettaiji.azurecr.io/ai-agents/jina-search-agent:latest", - "port": 8080, + "port": 8000, "agent_framework": "api", "env_requirements": { "required": { @@ -94,7 +94,7 @@ DEFAULT_TEMPLATES = { "display_name": "Azure Blob Agent", "description": "Azure Blob 存储 Agent", "image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest", - "port": 8080, + "port": 8000, "agent_framework": "api", "env_requirements": { "required": { @@ -106,7 +106,7 @@ DEFAULT_TEMPLATES = { "display_name": "Azure Blob Agent MCP", "description": "Azure Blob 存储 Agent (MCP 协议)", "image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-mcp:latest", - "port": 8080, + "port": 8000, "agent_framework": "mcp", "env_requirements": { "required": { @@ -118,7 +118,7 @@ DEFAULT_TEMPLATES = { "display_name": "Azure Blob Agent A2A", "description": "Azure Blob 存储 Agent (A2A 协议)", "image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-a2a:latest", - "port": 8080, + "port": 8000, "agent_framework": "a2a", "env_requirements": { "required": { @@ -130,7 +130,7 @@ DEFAULT_TEMPLATES = { "display_name": "A2A LiteLLM Agent", "description": "LiteLLM A2A 协议 Agent", "image": "agnettaiji.azurecr.io/ai-agents/a2a-litellm-agent:latest", - "port": 8080, + "port": 8000, "agent_framework": "a2a", "env_requirements": {}, }, diff --git a/tool_generator_api.py b/tool_generator_api.py index 8808385..897a684 100644 --- a/tool_generator_api.py +++ b/tool_generator_api.py @@ -12,6 +12,7 @@ from datetime import datetime from typing import Dict, List, Optional, Any from fastapi import APIRouter, HTTPException, BackgroundTasks from pydantic import BaseModel, Field +from k8s_manager import sanitize_k8s_name from gitee_manager import gitee_manager from agent_code_generator import agent_code_generator @@ -185,9 +186,9 @@ async def generate_agent(request: GenerateAgentRequest, background_tasks: Backgr "timeout": tool.timeout }) - # 生成完整项目文件(参考 http://gitee.ath.cx:3000/xiaohei/cicd-AKS) + # 生成完整项目文件(k8s_name 必须合规,否则 Service 创建会报 DNS-1035) project_files = agent_code_generator.generate_full_project( - agent_name=request.agent_name, + agent_name=k8s_name_for_cicd, description=request.description, tools_config=tools_config, auto_deploy=request.auto_deploy @@ -261,9 +262,9 @@ async def generate_agent(request: GenerateAgentRequest, background_tasks: Backgr "created_at": datetime.utcnow().isoformat(), "status": "building", "auto_deploy": request.auto_deploy, - "image_name": f"agnettaiji.azurecr.io/ai-agents/{repo_name}:latest", - "expected_domain": f"{k8s_name}.taijiagnet.com", - "expected_namespace": f"agent-{k8s_name}" + "image_name": f"agnettaiji.azurecr.io/ai-agents/{k8s_name_for_cicd}:latest", + "expected_domain": f"{k8s_name_for_cicd}.taijiagnet.com", + "expected_namespace": f"agent-{k8s_name_for_cicd}" } logger.info(f"✅ Agent 项目创建成功: {repo_name}") @@ -278,12 +279,12 @@ async def generate_agent(request: GenerateAgentRequest, background_tasks: Backgr "agent_ref_id": agent_ref_id, "repo_name": repo_name, "repo_url": repo_result.get("html_url"), - "image_name": f"agnettaiji.azurecr.io/ai-agents/{repo_name}:latest", + "image_name": f"agnettaiji.azurecr.io/ai-agents/{k8s_name_for_cicd}:latest", "status": "building", "files_pushed": len(project_files), "tools_count": len(request.tools), "expected_domain": expected_domain, - "expected_namespace": f"agent-{k8s_name}" + "expected_namespace": f"agent-{k8s_name_for_cicd}" }, "message": f"Agent 项目已创建并推送到 Gitee,CI/CD 正在构建中。部署后访问: http://{expected_domain}" }