fix: 统一 agent 默认端口为 8000(search_agent 系列保持 8080)

- 修改 template_manager.py、k8s_manager.py 中的端口映射
- 更新 jina_search_agent、azure_blob_agent 系列、a2a_litellm_agent 的代码和 Dockerfile 为 8000
- 添加端口修改脚本和测试脚本

Made-with: Cursor
This commit is contained in:
zhanggangyong
2026-03-02 15:13:07 +00:00
parent ca6a30bdab
commit 33955b68dc
22 changed files with 611 additions and 64 deletions
+4 -3
View File
@@ -12,6 +12,7 @@ import re
import requests import requests
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from datetime import datetime from datetime import datetime
from k8s_manager import sanitize_k8s_name
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1646,7 +1647,7 @@ class CallbackContextManager:
replicas: 副本数量 replicas: 副本数量
tool_api_keys: 工具 API 密钥列表(将注入到容器环境变量) 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}" image_repo = f"{self.acr_namespace}/{k8s_name}"
# 生成工具 API Key 环境变量配置 # 生成工具 API Key 环境变量配置
@@ -1914,7 +1915,7 @@ jobs:
) -> str: ) -> str:
"""生成 README.md""" """生成 README.md"""
tools_doc = "\n".join([f"- **{t.get('name')}**: {t.get('description', '')}" for t in tools]) 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 = "" deploy_doc = ""
if auto_deploy: if auto_deploy:
@@ -2053,7 +2054,7 @@ MIT License
""" """
files = {} files = {}
k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") k8s_name = sanitize_k8s_name(agent_name)
# 生成 src/server/mcp_server.py # 生成 src/server/mcp_server.py
files["src/server/mcp_server.py"] = self.generate_mcp_server( files["src/server/mcp_server.py"] = self.generate_mcp_server(
@@ -24,7 +24,7 @@ COPY agents/a2a_litellm_agent/*.py /app/
# 设置环境变量 # 设置环境变量
ENV SERVICE_HOST=0.0.0.0 ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8080 ENV SERVICE_PORT=8000
ENV POD_NAME=a2a-litellm-agent ENV POD_NAME=a2a-litellm-agent
ENV TEMPLATE_TYPE=a2a_litellm_agent ENV TEMPLATE_TYPE=a2a_litellm_agent
ENV PYTHONUNBUFFERED=1 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 \ 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"] CMD ["python", "main.py"]
@@ -26,7 +26,7 @@ logger = structlog.get_logger()
# 环境变量配置 # 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") 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") POD_NAME = os.getenv("POD_NAME", "a2a-litellm-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "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启动) 创建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 LITELLM_API_KEY="your-key"
export MODEL_NAME="your-model" 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) server = A2AAgentServer(api_key=api_key, model=model)
return server.app return server.app
@@ -70,7 +70,7 @@ class AgentConfig:
version: str = "1.0.0" version: str = "1.0.0"
# 服务端口 # 服务端口
port: int = 8080 port: int = 8000
# 服务主机 # 服务主机
host: str = "0.0.0.0" host: str = "0.0.0.0"
@@ -8,7 +8,7 @@ from a2a_server import create_app
# 环境变量配置 # 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") 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") POD_NAME = os.getenv("POD_NAME", "a2a-litellm-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "a2a_litellm_agent") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "a2a_litellm_agent")
@@ -26,11 +26,11 @@ COPY common/api_key_utils.py /app/common/
# 设置环境变量 # 设置环境变量
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV SERVICE_HOST=0.0.0.0 ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8080 ENV SERVICE_PORT=8000
# 健康检查 - 使用Python避免僵尸进程 # 健康检查 - 使用Python避免僵尸进程
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ 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) # 运行agent (直接使用Python,避免shell)
CMD ["python3", "-u", "azure_blob_agent.py"] CMD ["python3", "-u", "azure_blob_agent.py"]
@@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
# 环境变量配置 # 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") 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") POD_NAME = os.getenv("POD_NAME", "azure-blob-agent")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent")
@@ -19,11 +19,11 @@ COPY agents/azure_blob_agent_a2a/azure_blob_agent_a2a.py /app/
COPY common/api_key_utils.py /app/common/ COPY common/api_key_utils.py /app/common/
# 暴露端口 # 暴露端口
EXPOSE 8080 EXPOSE 8000
# 健康检查 # 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ 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"] CMD ["python", "azure_blob_agent_a2a.py"]
@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
# 环境变量配置 # 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") 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") POD_NAME = os.getenv("POD_NAME", "azure-blob-agent-a2a")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_a2a") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_a2a")
AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "a2a") AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "a2a")
@@ -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/ 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 \ 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"] CMD ["python", "azure_blob_agent_mcp.py"]
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
# 环境变量配置 # 环境变量配置
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") 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") POD_NAME = os.getenv("POD_NAME", "azure-blob-agent-mcp")
TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_mcp") TEMPLATE_TYPE = os.getenv("TEMPLATE_TYPE", "azure_blob_agent_mcp")
AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "mcp") AGENT_FRAMEWORK = os.getenv("AGENT_FRAMEWORK", "mcp")
@@ -25,7 +25,7 @@ COPY agents/jina_search_agent/jina_search_agent.py /app/
# 环境变量 # 环境变量
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV SERVICE_HOST=0.0.0.0 ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8080 ENV SERVICE_PORT=8000
# 默认 Jina API Key (硬编码) # 默认 Jina API Key (硬编码)
ENV JINA_API_KEY=jina_e26dc30420a44a1e859216528065b203TkMRmsoz-FgMDQC5FZX9jr5oF2CI 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 \ 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"] CMD ["python3", "-u", "jina_search_agent.py"]
@@ -31,7 +31,7 @@ logger = logging.getLogger(__name__)
# 环境变量 # 环境变量
SERVICE_HOST = os.getenv("SERVICE_HOST", "0.0.0.0") 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") POD_NAME = os.getenv("POD_NAME", "jina-search-agent")
USER_ID = os.getenv("USER_ID", "") USER_ID = os.getenv("USER_ID", "")
JINA_API_KEY = os.getenv("JINA_API_KEY", "") JINA_API_KEY = os.getenv("JINA_API_KEY", "")
+11 -2
View File
@@ -9,7 +9,7 @@ from sqlalchemy.orm import Session
from datetime import datetime from datetime import datetime
import logging import logging
from k8s_manager import K8sManager from k8s_manager import K8sManager, sanitize_k8s_name
from database import ( from database import (
get_db, Template, Agent, Quota, AgentMetric, get_db, Template, Agent, Quota, AgentMetric,
AgentType, AgentStatus, parse_resource_string 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)}" 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
config_data = request.config.copy() config_data = request.config.copy()
config_data["agent_framework"] = framework # 添加框架类型到配置 config_data["agent_framework"] = framework # 添加框架类型到配置
@@ -587,8 +593,11 @@ async def delete_agent(agent_name: str, db: Session = Depends(get_db)):
try: try:
logger.info(f"收到删除Agent请求: {agent_name}") logger.info(f"收到删除Agent请求: {agent_name}")
# DNS-1035 名称合规化
agent_name = sanitize_k8s_name(agent_name)
# 保护机制:防止删除 agent-manager 命名空间 # 保护机制:防止删除 agent-manager 命名空间
computed_namespace = f"agent-{agent_name}".lower().strip('-')[:63] computed_namespace = f"agent-{agent_name}"[:63].rstrip('-')
if computed_namespace == "agent-manager": if computed_namespace == "agent-manager":
logger.error(f"❌ 禁止删除 agent-manager 命名空间!agent_name={agent_name}, computed_namespace={computed_namespace}") logger.error(f"❌ 禁止删除 agent-manager 命名空间!agent_name={agent_name}, computed_namespace={computed_namespace}")
raise HTTPException( raise HTTPException(
+125
View File
@@ -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**。
+8 -7
View File
@@ -21,6 +21,7 @@ from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from agent_code_generator import agent_code_generator from agent_code_generator import agent_code_generator
from k8s_manager import sanitize_k8s_name
from tool_storage import tool_storage from tool_storage import tool_storage
from gitee_manager import gitee_manager from gitee_manager import gitee_manager
@@ -860,10 +861,10 @@ async def create_agent_with_tools(request: CreateAgentWithToolsRequest):
# 生成唯一后缀,确保不同用户创建同名 Agent 不会冲突 # 生成唯一后缀,确保不同用户创建同名 Agent 不会冲突
unique_suffix = uuid.uuid4().hex[:6] 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 带唯一后缀,避免域名/namespace 冲突(确保 DNS-1035 合规)
k8s_name = f"{base_name}-{unique_suffix}" k8s_name = sanitize_k8s_name(f"{base_name}-{unique_suffix}")
repo_name = f"agent-{k8s_name}" repo_name = f"agent-{k8s_name}"
agent_ref_id = f"agent-{repo_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"): if agent_info and agent_info.get("k8s_name"):
k8s_name = agent_info.get("k8s_name") k8s_name = agent_info.get("k8s_name")
else: else:
# 无法从 repo_name 准确推断,使用 agent 名称 # 无法从 repo_name 准确推断,使用 agent 名称(确保 DNS-1035 合规)
agent_name = agent_info.get("name") if agent_info else None agent_name = agent_info.get("name") if agent_info else None
if agent_name: if agent_name:
k8s_name = agent_name.lower().replace("_", "-").replace(" ", "-") k8s_name = sanitize_k8s_name(agent_name)
else: else:
k8s_name = repo_name.lower().replace("_", "-").replace(" ", "-") k8s_name = sanitize_k8s_name(repo_name)
expected_domain = f"{k8s_name}.taijiagnet.com" expected_domain = f"{k8s_name}.taijiagnet.com"
namespace = f"agent-{k8s_name}" 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) repo_name = agent_ref_id.replace("agent-", "", 1)
else: else:
repo_name = agent_ref_id repo_name = agent_ref_id
k8s_name = repo_name.lower().replace("_", "-").replace(" ", "-") k8s_name = sanitize_k8s_name(repo_name)
namespace = f"agent-{k8s_name}" namespace = f"agent-{k8s_name}"
# 导入 K8sManager 查询实际状态 # 导入 K8sManager 查询实际状态
+55 -16
View File
@@ -6,6 +6,7 @@ from kubernetes.client.rest import ApiException
from typing import Dict, List, Optional from typing import Dict, List, Optional
import logging import logging
import os import os
import re
import requests import requests
import time import time
@@ -13,6 +14,45 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) 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: class K8sManager:
"""Kubernetes资源管理器""" """Kubernetes资源管理器"""
@@ -94,11 +134,9 @@ class K8sManager:
Returns: Returns:
创建的命名空间名称 创建的命名空间名称
""" """
# 生成命名空间名称(使用 agent-{agent_name} 格式) # 生成命名空间名称(使用 agent-{sanitized_name} 格式,确保 DNS 合规)
namespace_name = f"agent-{agent_name}" sanitized_name = sanitize_k8s_name(agent_name)
namespace_name = f"agent-{sanitized_name}"[:63].rstrip('-')
# 确保命名空间名称符合 DNS 标准(最多 63 个字符,只能包含小写字母、数字和连字符)
namespace_name = namespace_name[:63].lower().strip('-')
try: try:
# 检查命名空间是否已存在 # 检查命名空间是否已存在
@@ -318,11 +356,11 @@ class K8sManager:
"search_agent_mcp": 8080, "search_agent_mcp": 8080,
"mysql_agent": 8000, "mysql_agent": 8000,
"postgresql_agent": 8000, "postgresql_agent": 8000,
"jina_search_agent": 8080, "jina_search_agent": 8000,
"azure_blob_agent": 8080, "azure_blob_agent": 8000,
"azure_blob_agent_mcp": 8080, "azure_blob_agent_mcp": 8000,
"azure_blob_agent_a2a": 8080, "azure_blob_agent_a2a": 8000,
"a2a_litellm_agent": 8080, "a2a_litellm_agent": 8000,
"code_ai_agent": 8000, "code_ai_agent": 8000,
"facebook_agent": 8000, "facebook_agent": 8000,
"media_downloader": 8000, "media_downloader": 8000,
@@ -340,7 +378,7 @@ class K8sManager:
"JINA_API_KEY": "Jina API密钥,从 https://jina.ai/ 获取" "JINA_API_KEY": "Jina API密钥,从 https://jina.ai/ 获取"
}, },
"optional": { "optional": {
"SERVICE_PORT": "HTTP服务端口,默认8080", "SERVICE_PORT": "HTTP服务端口,默认8000",
"SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0" "SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0"
} }
}, },
@@ -376,7 +414,7 @@ class K8sManager:
}, },
"optional": { "optional": {
"AZURE_STORAGE_CONNECTION_STRING": "Azure Storage连接字符串(可选,也可通过 /connect API 动态传入)", "AZURE_STORAGE_CONNECTION_STRING": "Azure Storage连接字符串(可选,也可通过 /connect API 动态传入)",
"SERVICE_PORT": "HTTP服务端口,默认8080", "SERVICE_PORT": "HTTP服务端口,默认8000",
"SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0" "SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0"
} }
}, },
@@ -479,7 +517,7 @@ class K8sManager:
"LITELLM_API_KEY": "LiteLLM API 密钥(可在请求中传入)", "LITELLM_API_KEY": "LiteLLM API 密钥(可在请求中传入)",
"AGENT_NAME": "Agent 名称", "AGENT_NAME": "Agent 名称",
"AGENT_DESCRIPTION": "Agent 描述", "AGENT_DESCRIPTION": "Agent 描述",
"SERVICE_PORT": "HTTP服务端口,默认 8080", "SERVICE_PORT": "HTTP服务端口,默认 8000",
"SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0" "SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0"
} }
}, },
@@ -706,9 +744,9 @@ class K8sManager:
# 设置容器端口(如果是HTTP服务类型的agent) # 设置容器端口(如果是HTTP服务类型的agent)
container_ports = None 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)] 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)] container_ports = [client.V1ContainerPort(container_port=8000)]
# 创建Pod规格 # 创建Pod规格
@@ -785,7 +823,8 @@ class K8sManager:
Returns: 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 # region agent log
try: try:
import json, time import json, time
+107
View File
@@ -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 "=========================================="
+148
View File
@@ -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 "=========================================="
+116
View File
@@ -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()
+8 -8
View File
@@ -28,7 +28,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Search Agent", "display_name": "Search Agent",
"description": "搜索 Agent (LangChain)", "description": "搜索 Agent (LangChain)",
"image": "agnettaiji.azurecr.io/ai-agents/search-agent:latest", "image": "agnettaiji.azurecr.io/ai-agents/search-agent:latest",
"port": 8000, "port": 8080,
"agent_framework": "langchain", "agent_framework": "langchain",
"env_requirements": {}, "env_requirements": {},
}, },
@@ -36,7 +36,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Search Agent A2A", "display_name": "Search Agent A2A",
"description": "搜索 Agent (A2A 协议)", "description": "搜索 Agent (A2A 协议)",
"image": "agnettaiji.azurecr.io/ai-agents/search-agent-a2a:latest", "image": "agnettaiji.azurecr.io/ai-agents/search-agent-a2a:latest",
"port": 8000, "port": 8080,
"agent_framework": "a2a", "agent_framework": "a2a",
"env_requirements": {}, "env_requirements": {},
}, },
@@ -44,7 +44,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Search Agent MCP", "display_name": "Search Agent MCP",
"description": "搜索 Agent (MCP 协议)", "description": "搜索 Agent (MCP 协议)",
"image": "agnettaiji.azurecr.io/ai-agents/search-agent-mcp:latest", "image": "agnettaiji.azurecr.io/ai-agents/search-agent-mcp:latest",
"port": 8000, "port": 8080,
"agent_framework": "mcp", "agent_framework": "mcp",
"env_requirements": {}, "env_requirements": {},
}, },
@@ -82,7 +82,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Jina Search Agent", "display_name": "Jina Search Agent",
"description": "Jina AI 搜索 Agent", "description": "Jina AI 搜索 Agent",
"image": "agnettaiji.azurecr.io/ai-agents/jina-search-agent:latest", "image": "agnettaiji.azurecr.io/ai-agents/jina-search-agent:latest",
"port": 8080, "port": 8000,
"agent_framework": "api", "agent_framework": "api",
"env_requirements": { "env_requirements": {
"required": { "required": {
@@ -94,7 +94,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Azure Blob Agent", "display_name": "Azure Blob Agent",
"description": "Azure Blob 存储 Agent", "description": "Azure Blob 存储 Agent",
"image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest", "image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent:latest",
"port": 8080, "port": 8000,
"agent_framework": "api", "agent_framework": "api",
"env_requirements": { "env_requirements": {
"required": { "required": {
@@ -106,7 +106,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Azure Blob Agent MCP", "display_name": "Azure Blob Agent MCP",
"description": "Azure Blob 存储 Agent (MCP 协议)", "description": "Azure Blob 存储 Agent (MCP 协议)",
"image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-mcp:latest", "image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-mcp:latest",
"port": 8080, "port": 8000,
"agent_framework": "mcp", "agent_framework": "mcp",
"env_requirements": { "env_requirements": {
"required": { "required": {
@@ -118,7 +118,7 @@ DEFAULT_TEMPLATES = {
"display_name": "Azure Blob Agent A2A", "display_name": "Azure Blob Agent A2A",
"description": "Azure Blob 存储 Agent (A2A 协议)", "description": "Azure Blob 存储 Agent (A2A 协议)",
"image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-a2a:latest", "image": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-a2a:latest",
"port": 8080, "port": 8000,
"agent_framework": "a2a", "agent_framework": "a2a",
"env_requirements": { "env_requirements": {
"required": { "required": {
@@ -130,7 +130,7 @@ DEFAULT_TEMPLATES = {
"display_name": "A2A LiteLLM Agent", "display_name": "A2A LiteLLM Agent",
"description": "LiteLLM A2A 协议 Agent", "description": "LiteLLM A2A 协议 Agent",
"image": "agnettaiji.azurecr.io/ai-agents/a2a-litellm-agent:latest", "image": "agnettaiji.azurecr.io/ai-agents/a2a-litellm-agent:latest",
"port": 8080, "port": 8000,
"agent_framework": "a2a", "agent_framework": "a2a",
"env_requirements": {}, "env_requirements": {},
}, },
+8 -7
View File
@@ -12,6 +12,7 @@ from datetime import datetime
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from fastapi import APIRouter, HTTPException, BackgroundTasks from fastapi import APIRouter, HTTPException, BackgroundTasks
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from k8s_manager import sanitize_k8s_name
from gitee_manager import gitee_manager from gitee_manager import gitee_manager
from agent_code_generator import agent_code_generator from agent_code_generator import agent_code_generator
@@ -185,9 +186,9 @@ async def generate_agent(request: GenerateAgentRequest, background_tasks: Backgr
"timeout": tool.timeout "timeout": tool.timeout
}) })
# 生成完整项目文件(参考 http://gitee.ath.cx:3000/xiaohei/cicd-AKS) # 生成完整项目文件(k8s_name 必须合规,否则 Service 创建会报 DNS-1035)
project_files = agent_code_generator.generate_full_project( project_files = agent_code_generator.generate_full_project(
agent_name=request.agent_name, agent_name=k8s_name_for_cicd,
description=request.description, description=request.description,
tools_config=tools_config, tools_config=tools_config,
auto_deploy=request.auto_deploy auto_deploy=request.auto_deploy
@@ -261,9 +262,9 @@ async def generate_agent(request: GenerateAgentRequest, background_tasks: Backgr
"created_at": datetime.utcnow().isoformat(), "created_at": datetime.utcnow().isoformat(),
"status": "building", "status": "building",
"auto_deploy": request.auto_deploy, "auto_deploy": request.auto_deploy,
"image_name": f"agnettaiji.azurecr.io/ai-agents/{repo_name}:latest", "image_name": f"agnettaiji.azurecr.io/ai-agents/{k8s_name_for_cicd}:latest",
"expected_domain": f"{k8s_name}.taijiagnet.com", "expected_domain": f"{k8s_name_for_cicd}.taijiagnet.com",
"expected_namespace": f"agent-{k8s_name}" "expected_namespace": f"agent-{k8s_name_for_cicd}"
} }
logger.info(f"✅ Agent 项目创建成功: {repo_name}") 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, "agent_ref_id": agent_ref_id,
"repo_name": repo_name, "repo_name": repo_name,
"repo_url": repo_result.get("html_url"), "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", "status": "building",
"files_pushed": len(project_files), "files_pushed": len(project_files),
"tools_count": len(request.tools), "tools_count": len(request.tools),
"expected_domain": expected_domain, "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}" "message": f"Agent 项目已创建并推送到 Gitee,CI/CD 正在构建中。部署后访问: http://{expected_domain}"
} }