feat: 新增外部工具 API 及回调功能(计费)
主要更新: - 新增 external_tool_api.py: 外部工具管理 API - 新增 tool_storage.py: 工具存储管理器 - 新增回调功能用于计费 (agent_callback_utils) - 支持多工具创建 Agent - 新增 CI/CD 构建状态查询 API - 新增部署信息查询 API - 更新文档 (EXTERNAL_TOOL_API.md v2.0) - 更新 Dockerfile 添加新模块 - 更新 app.py 集成外部工具路由
This commit is contained in:
@@ -16,8 +16,13 @@ COPY template_manager.py .
|
||||
COPY gitee_manager.py .
|
||||
COPY agent_code_generator.py .
|
||||
COPY tool_generator_api.py .
|
||||
COPY external_tool_api.py .
|
||||
COPY tool_storage.py .
|
||||
COPY agent_manager/ ./agent_manager/
|
||||
|
||||
# 创建工具存储目录
|
||||
RUN mkdir -p /app/tool_storage
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
|
||||
+537
-19
@@ -101,6 +101,135 @@ class AgentCodeGenerator:
|
||||
}
|
||||
return type_map.get(json_type, "Any")
|
||||
|
||||
def generate_tool_code(self, tool_config: dict) -> str:
|
||||
"""
|
||||
根据工具配置生成 Pydantic 工具代码
|
||||
|
||||
Args:
|
||||
tool_config: 包含 name, url, method, auth, request_params 等
|
||||
|
||||
Returns:
|
||||
生成的 Python 代码字符串
|
||||
"""
|
||||
name = tool_config.get("name", "custom_tool")
|
||||
func_name = self._convert_name_to_python(name)
|
||||
desc = tool_config.get("description", "")
|
||||
url = tool_config.get("url", "")
|
||||
method = tool_config.get("method", "GET").upper()
|
||||
auth = tool_config.get("auth", {})
|
||||
request_params = tool_config.get("request_params", {})
|
||||
request_body = tool_config.get("request_body", {})
|
||||
timeout = tool_config.get("timeout", 30)
|
||||
|
||||
# 构建参数
|
||||
params = []
|
||||
params_doc = []
|
||||
|
||||
# 支持两种格式:
|
||||
# 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}")
|
||||
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}")
|
||||
|
||||
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", "")'
|
||||
|
||||
# 生成函数代码
|
||||
code = f'''"""
|
||||
工具: {name}
|
||||
描述: {desc}
|
||||
自动生成时间: {datetime.now().isoformat()}
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from typing import Optional, Any
|
||||
import httpx
|
||||
|
||||
|
||||
async def {func_name}({params_str}) -> str:
|
||||
"""
|
||||
{desc}
|
||||
|
||||
Args:
|
||||
{params_doc_str}
|
||||
|
||||
Returns:
|
||||
API 响应结果 (JSON 格式)
|
||||
"""
|
||||
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:
|
||||
return json.dumps({{
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}}, ensure_ascii=False)
|
||||
'''
|
||||
return code
|
||||
|
||||
def generate_mcp_server(
|
||||
self,
|
||||
agent_name: str,
|
||||
@@ -377,23 +506,40 @@ if __name__ == '__main__':
|
||||
- 对于操作请求:返回操作状态和结果
|
||||
- 始终使用中文与用户交流(除非用户使用其他语言)'''
|
||||
|
||||
def generate_api_server(self, agent_name: str, description: str) -> str:
|
||||
def generate_api_server(self, agent_name: str, description: str, tools_config: List[Dict] = None) -> str:
|
||||
"""
|
||||
生成 API 服务器代码 (api_server.py)
|
||||
基于 _template/src/server/api_server.py 框架
|
||||
集成回调功能用于计费
|
||||
|
||||
Args:
|
||||
agent_name: Agent 名称
|
||||
description: Agent 描述
|
||||
tools_config: 工具配置列表(用于生成工具名称列表)
|
||||
"""
|
||||
# 生成工具名称列表(用于回调)
|
||||
tool_names = []
|
||||
if tools_config:
|
||||
for tool in tools_config:
|
||||
tool_names.append(tool.get("name", "unknown_tool"))
|
||||
|
||||
tool_names_str = json.dumps(tool_names, ensure_ascii=False)
|
||||
|
||||
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
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, AsyncGenerator, List
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request, Header, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -401,19 +547,47 @@ from fastapi.responses import StreamingResponse, JSONResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .mcp_server import TOOL_MAP, TOOL_LIST
|
||||
from .agent_callback_utils import AgentCallbackHandler, CallbackContextManager
|
||||
|
||||
# ==================== 配置 ====================
|
||||
|
||||
SERVER_NAME = "{agent_name}"
|
||||
POD_NAME = os.getenv("POD_NAME", "{agent_name}")
|
||||
USER_ID = os.getenv("USER_ID", "")
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 本 Agent 包含的工具列表
|
||||
AGENT_TOOLS = {tool_names_str}
|
||||
|
||||
# ==================== 回调处理器 ====================
|
||||
|
||||
callback_handler: Optional[AgentCallbackHandler] = None
|
||||
|
||||
|
||||
def get_callback_handler() -> AgentCallbackHandler:
|
||||
"""获取或创建回调处理器(单例)"""
|
||||
global callback_handler
|
||||
if callback_handler is None:
|
||||
callback_handler = AgentCallbackHandler(
|
||||
agent_name=POD_NAME,
|
||||
user_id=USER_ID
|
||||
)
|
||||
return callback_handler
|
||||
|
||||
|
||||
# ==================== FastAPI 应用 ====================
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
print(f"🚀 {{SERVER_NAME}} 启动")
|
||||
logger.info(f"🚀 {{SERVER_NAME}} 启动")
|
||||
logger.info(f" 包含工具: {{', '.join(AGENT_TOOLS)}}")
|
||||
logger.info(f" Pod 名称: {{POD_NAME}}")
|
||||
logger.info(f" 用户 ID: {{USER_ID or '未设置'}}")
|
||||
yield
|
||||
print(f"🛑 {{SERVER_NAME}} 关闭")
|
||||
logger.info(f"🛑 {{SERVER_NAME}} 关闭")
|
||||
|
||||
app = FastAPI(
|
||||
title=SERVER_NAME,
|
||||
@@ -467,13 +641,15 @@ async def root():
|
||||
return {{
|
||||
"service": SERVER_NAME,
|
||||
"status": "running",
|
||||
"tools": list(TOOL_MAP.keys())
|
||||
"tools": list(TOOL_MAP.keys()),
|
||||
"tools_count": len(TOOL_MAP),
|
||||
"pod_name": POD_NAME
|
||||
}}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {{"status": "healthy", "service": SERVER_NAME}}
|
||||
return {{"status": "healthy", "service": SERVER_NAME, "tools_count": len(TOOL_MAP)}}
|
||||
|
||||
|
||||
# ==================== MCP 端点 ====================
|
||||
@@ -481,8 +657,8 @@ async def health():
|
||||
sessions: Dict[str, Dict] = {{}}
|
||||
|
||||
|
||||
async def handle_mcp_request(data: Dict, session_id: str = None, api_key: str = None) -> Dict:
|
||||
"""处理 MCP JSON-RPC 请求"""
|
||||
async def handle_mcp_request(data: Dict, session_id: str = None, api_key: str = None, user_id: str = None) -> Dict:
|
||||
"""处理 MCP JSON-RPC 请求(带回调)"""
|
||||
method = data.get("method")
|
||||
params = data.get("params", {{}})
|
||||
req_id = data.get("id")
|
||||
@@ -515,8 +691,21 @@ async def handle_mcp_request(data: Dict, session_id: str = None, api_key: str =
|
||||
if api_key:
|
||||
os.environ['OPENAI_API_KEY'] = api_key
|
||||
|
||||
# 使用回调上下文管理器(如果有 user_id)
|
||||
effective_user_id = user_id or USER_ID
|
||||
|
||||
try:
|
||||
result = await TOOL_MAP[tool_name](**args)
|
||||
if effective_user_id:
|
||||
handler = get_callback_handler()
|
||||
with CallbackContextManager(
|
||||
handler=handler,
|
||||
user_id=effective_user_id,
|
||||
request_id=f"mcp-{{req_id}}-{{int(datetime.utcnow().timestamp())}}"
|
||||
) as ctx:
|
||||
ctx.add_tool(tool_name)
|
||||
result = await TOOL_MAP[tool_name](**args)
|
||||
else:
|
||||
result = await TOOL_MAP[tool_name](**args)
|
||||
finally:
|
||||
if old_key:
|
||||
os.environ['OPENAI_API_KEY'] = old_key
|
||||
@@ -543,7 +732,8 @@ async def mcp_endpoint(request: Request):
|
||||
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)
|
||||
user_id = request.headers.get("x-user-id") or USER_ID
|
||||
response = await handle_mcp_request(body, session_id, api_key, user_id)
|
||||
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)}}}})
|
||||
@@ -572,9 +762,10 @@ async def mcp_sse_post(request: Request):
|
||||
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)
|
||||
user_id = request.headers.get("x-user-id") or USER_ID
|
||||
|
||||
async def stream() -> AsyncGenerator[str, None]:
|
||||
response = await handle_mcp_request(body, session_id, api_key)
|
||||
response = await handle_mcp_request(body, session_id, api_key, user_id)
|
||||
yield f"data: {{json.dumps(response)}}\\n\\n"
|
||||
|
||||
return StreamingResponse(stream(), media_type="text/event-stream",
|
||||
@@ -589,6 +780,13 @@ class ToolCallRequest(BaseModel):
|
||||
"""工具调用请求"""
|
||||
tool_name: str = Field(..., description="工具名称")
|
||||
parameters: Dict[str, Any] = Field(default={{}}, description="工具参数")
|
||||
user_id: Optional[str] = Field(None, description="用户ID(用于计费回调)")
|
||||
|
||||
|
||||
class MultiToolCallRequest(BaseModel):
|
||||
"""批量工具调用请求"""
|
||||
calls: List[ToolCallRequest] = Field(..., description="工具调用列表")
|
||||
user_id: Optional[str] = Field(None, description="用户ID(用于计费回调)")
|
||||
|
||||
|
||||
class ToolCallResponse(BaseModel):
|
||||
@@ -596,6 +794,15 @@ class ToolCallResponse(BaseModel):
|
||||
success: bool
|
||||
result: Optional[Any] = None
|
||||
error: Optional[str] = None
|
||||
tools_used: Optional[List[str]] = None
|
||||
|
||||
|
||||
class MultiToolCallResponse(BaseModel):
|
||||
"""批量工具调用响应"""
|
||||
success: bool
|
||||
results: List[ToolCallResponse]
|
||||
tools_used: List[str]
|
||||
total_calls: int
|
||||
|
||||
|
||||
@app.get("/tools")
|
||||
@@ -605,28 +812,117 @@ async def list_tools():
|
||||
"tools": [
|
||||
{{"name": t["name"], "description": t["description"]}}
|
||||
for t in TOOL_LIST
|
||||
]
|
||||
],
|
||||
"count": len(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}} 不存在")
|
||||
|
||||
effective_user_id = request.user_id or USER_ID
|
||||
tools_used = [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)
|
||||
# 使用回调上下文管理器
|
||||
if effective_user_id:
|
||||
handler = get_callback_handler()
|
||||
with CallbackContextManager(
|
||||
handler=handler,
|
||||
user_id=effective_user_id,
|
||||
request_id=f"api-{{int(datetime.utcnow().timestamp())}}"
|
||||
) as ctx:
|
||||
ctx.add_tool(request.tool_name)
|
||||
result = await TOOL_MAP[request.tool_name](**request.parameters)
|
||||
else:
|
||||
result = await TOOL_MAP[request.tool_name](**request.parameters)
|
||||
|
||||
return ToolCallResponse(
|
||||
success=True,
|
||||
result=json.loads(result) if isinstance(result, str) else result,
|
||||
tools_used=tools_used
|
||||
)
|
||||
finally:
|
||||
if old_key:
|
||||
os.environ['OPENAI_API_KEY'] = old_key
|
||||
except Exception as e:
|
||||
return ToolCallResponse(success=False, error=str(e))
|
||||
logger.error(f"工具调用失败: {{e}}")
|
||||
return ToolCallResponse(success=False, error=str(e), tools_used=tools_used)
|
||||
|
||||
|
||||
@app.post("/tools/batch-call", response_model=MultiToolCallResponse)
|
||||
async def batch_call_tools(request: MultiToolCallRequest, api_key: str = Depends(verify_api_key)):
|
||||
"""批量调用多个工具(带计费回调)"""
|
||||
effective_user_id = request.user_id or USER_ID
|
||||
results = []
|
||||
tools_used = []
|
||||
|
||||
# 设置 API Key
|
||||
old_key = os.environ.get('OPENAI_API_KEY')
|
||||
os.environ['OPENAI_API_KEY'] = api_key
|
||||
|
||||
try:
|
||||
# 使用回调上下文管理器
|
||||
if effective_user_id:
|
||||
handler = get_callback_handler()
|
||||
with CallbackContextManager(
|
||||
handler=handler,
|
||||
user_id=effective_user_id,
|
||||
request_id=f"batch-{{int(datetime.utcnow().timestamp())}}"
|
||||
) as ctx:
|
||||
for call in request.calls:
|
||||
if call.tool_name not in TOOL_MAP:
|
||||
results.append(ToolCallResponse(
|
||||
success=False,
|
||||
error=f"工具 {{call.tool_name}} 不存在"
|
||||
))
|
||||
continue
|
||||
|
||||
try:
|
||||
ctx.add_tool(call.tool_name)
|
||||
tools_used.append(call.tool_name)
|
||||
result = await TOOL_MAP[call.tool_name](**call.parameters)
|
||||
results.append(ToolCallResponse(
|
||||
success=True,
|
||||
result=json.loads(result) if isinstance(result, str) else result
|
||||
))
|
||||
except Exception as e:
|
||||
results.append(ToolCallResponse(success=False, error=str(e)))
|
||||
else:
|
||||
for call in request.calls:
|
||||
if call.tool_name not in TOOL_MAP:
|
||||
results.append(ToolCallResponse(
|
||||
success=False,
|
||||
error=f"工具 {{call.tool_name}} 不存在"
|
||||
))
|
||||
continue
|
||||
|
||||
try:
|
||||
tools_used.append(call.tool_name)
|
||||
result = await TOOL_MAP[call.tool_name](**call.parameters)
|
||||
results.append(ToolCallResponse(
|
||||
success=True,
|
||||
result=json.loads(result) if isinstance(result, str) else result
|
||||
))
|
||||
except Exception as e:
|
||||
results.append(ToolCallResponse(success=False, error=str(e)))
|
||||
finally:
|
||||
if old_key:
|
||||
os.environ['OPENAI_API_KEY'] = old_key
|
||||
|
||||
return MultiToolCallResponse(
|
||||
success=all(r.success for r in results),
|
||||
results=results,
|
||||
tools_used=list(set(tools_used)),
|
||||
total_calls=len(request.calls)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -701,11 +997,221 @@ uvicorn[standard]>=0.27.0
|
||||
# HTTP Client
|
||||
httpx>=0.25.0
|
||||
aiohttp>=3.9.0
|
||||
requests>=2.31.0
|
||||
|
||||
# 其他
|
||||
python-dotenv>=1.0.0
|
||||
'''
|
||||
|
||||
def generate_callback_utils(self) -> str:
|
||||
"""生成回调工具代码 (用于计费)"""
|
||||
return '''"""
|
||||
Agent回调工具 - 用于向Agent Manager回调运行时长记录(计费)
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
from typing import Optional, List
|
||||
from datetime import datetime, timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentCallbackHandler:
|
||||
"""Agent回调处理器"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agent_name: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
callback_url: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
初始化回调处理器
|
||||
|
||||
Args:
|
||||
agent_name: Agent名称,默认从环境变量 POD_NAME 获取
|
||||
user_id: 用户ID,默认从环境变量 USER_ID 获取
|
||||
callback_url: 回调URL,默认从环境变量 AGENT_CALLBACK_URL 获取
|
||||
"""
|
||||
self.agent_name = agent_name or os.getenv("POD_NAME", "unknown-agent")
|
||||
self.user_id = user_id or os.getenv("USER_ID", "")
|
||||
self.callback_url = callback_url or os.getenv(
|
||||
"AGENT_CALLBACK_URL",
|
||||
"http://mcp-server.taiji-ai.svc.cluster.local:8000/api/v1/billing/agent-callback"
|
||||
)
|
||||
|
||||
self.start_time: Optional[datetime] = None
|
||||
self.tools_used: List[str] = []
|
||||
self.request_id: Optional[str] = None
|
||||
|
||||
logger.info(f"AgentCallbackHandler initialized: agent={self.agent_name}, callback_url={self.callback_url}")
|
||||
|
||||
def start_request(self, request_id: Optional[str] = None, user_id: Optional[str] = None):
|
||||
"""
|
||||
开始一次请求处理
|
||||
|
||||
Args:
|
||||
request_id: 请求ID
|
||||
user_id: 用户ID(如果提供则覆盖默认值)
|
||||
"""
|
||||
self.start_time = datetime.now(timezone.utc)
|
||||
self.tools_used = []
|
||||
self.request_id = request_id or f"req-{int(time.time())}"
|
||||
|
||||
if user_id:
|
||||
self.user_id = user_id
|
||||
|
||||
logger.info(f"Request started: request_id={self.request_id}, user_id={self.user_id}")
|
||||
|
||||
def add_tool_used(self, tool_name: str):
|
||||
"""
|
||||
记录使用的工具
|
||||
|
||||
Args:
|
||||
tool_name: 工具名称
|
||||
"""
|
||||
if tool_name not in self.tools_used:
|
||||
self.tools_used.append(tool_name)
|
||||
logger.debug(f"Tool used: {tool_name}")
|
||||
|
||||
def end_request(self, tools_used: Optional[List[str]] = None) -> bool:
|
||||
"""
|
||||
结束请求并发送回调
|
||||
|
||||
Args:
|
||||
tools_used: 使用的工具列表(可选,如果提供则覆盖内部记录)
|
||||
|
||||
Returns:
|
||||
是否成功发送回调
|
||||
"""
|
||||
if not self.start_time:
|
||||
logger.warning("Cannot end request: no start time recorded")
|
||||
return False
|
||||
|
||||
if not self.user_id:
|
||||
logger.warning("Cannot send callback: user_id not set")
|
||||
return False
|
||||
|
||||
end_time = datetime.now(timezone.utc)
|
||||
running_time = (end_time - self.start_time).total_seconds()
|
||||
|
||||
# 使用提供的工具列表或内部记录
|
||||
final_tools_used = tools_used if tools_used is not None else self.tools_used
|
||||
|
||||
# 发送回调
|
||||
success = self._send_callback(
|
||||
running_time_seconds=int(running_time),
|
||||
start_time=self.start_time,
|
||||
end_time=end_time,
|
||||
tools_used=final_tools_used
|
||||
)
|
||||
|
||||
# 重置状态
|
||||
self.start_time = None
|
||||
self.tools_used = []
|
||||
self.request_id = None
|
||||
|
||||
return success
|
||||
|
||||
def _send_callback(
|
||||
self,
|
||||
running_time_seconds: int,
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
tools_used: List[str]
|
||||
) -> bool:
|
||||
"""
|
||||
发送回调到Agent Manager
|
||||
|
||||
Args:
|
||||
running_time_seconds: 运行时长(秒)
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
tools_used: 使用的工具列表
|
||||
|
||||
Returns:
|
||||
是否成功发送
|
||||
"""
|
||||
try:
|
||||
payload = {
|
||||
"agentName": self.agent_name,
|
||||
"userId": self.user_id,
|
||||
"podRunningTimeSeconds": running_time_seconds,
|
||||
"toolsUsed": tools_used,
|
||||
"startTime": start_time.isoformat(),
|
||||
"endTime": end_time.isoformat(),
|
||||
"requestId": self.request_id
|
||||
}
|
||||
|
||||
logger.info(f"Sending callback: {payload}")
|
||||
|
||||
response = requests.post(
|
||||
self.callback_url,
|
||||
json=payload,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info(f"Callback sent successfully: {response.json()}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Callback failed with status {response.status_code}: {response.text}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Failed to send callback: {str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error sending callback: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
class CallbackContextManager:
|
||||
"""回调上下文管理器 - 使用with语句自动处理开始和结束"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
handler: AgentCallbackHandler,
|
||||
request_id: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
tools_used: Optional[List[str]] = None
|
||||
):
|
||||
"""
|
||||
初始化上下文管理器
|
||||
|
||||
Args:
|
||||
handler: AgentCallbackHandler实例
|
||||
request_id: 请求ID
|
||||
user_id: 用户ID
|
||||
tools_used: 使用的工具列表(可选)
|
||||
"""
|
||||
self.handler = handler
|
||||
self.request_id = request_id
|
||||
self.user_id = user_id
|
||||
self.tools_used = tools_used or []
|
||||
|
||||
def __enter__(self):
|
||||
"""进入上下文时开始计时"""
|
||||
self.handler.start_request(
|
||||
request_id=self.request_id,
|
||||
user_id=self.user_id
|
||||
)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""退出上下文时发送回调"""
|
||||
self.handler.end_request(tools_used=self.tools_used)
|
||||
return False # 不抑制异常
|
||||
|
||||
def add_tool(self, tool_name: str):
|
||||
"""添加使用的工具"""
|
||||
self.handler.add_tool_used(tool_name)
|
||||
if tool_name not in self.tools_used:
|
||||
self.tools_used.append(tool_name)
|
||||
'''
|
||||
|
||||
def generate_gitea_action(self, agent_name: str, auto_deploy: bool = True) -> str:
|
||||
"""
|
||||
生成 Gitea Actions CI/CD 配置
|
||||
@@ -1068,11 +1574,12 @@ MIT License
|
||||
"""
|
||||
生成完整的 Agent 项目文件
|
||||
基于 agent_templates/agents/_template 框架
|
||||
包含回调功能用于计费
|
||||
|
||||
Args:
|
||||
agent_name: Agent 名称
|
||||
description: Agent 描述
|
||||
tools_config: 工具配置列表
|
||||
tools_config: 工具配置列表(支持多个工具)
|
||||
auto_deploy: 是否自动部署到 AKS
|
||||
|
||||
Returns:
|
||||
@@ -1087,8 +1594,13 @@ MIT License
|
||||
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/api_server.py(传递 tools_config 用于回调)
|
||||
files["src/server/api_server.py"] = self.generate_api_server(
|
||||
agent_name, description, tools_config
|
||||
)
|
||||
|
||||
# 生成 src/server/agent_callback_utils.py(回调工具)
|
||||
files["src/server/agent_callback_utils.py"] = self.generate_callback_utils()
|
||||
|
||||
# 生成 src/server/__init__.py
|
||||
files["src/server/__init__.py"] = '"""服务器模块"""\n'
|
||||
@@ -1111,6 +1623,12 @@ MIT License
|
||||
# 生成 README
|
||||
files["README.md"] = self.generate_readme(agent_name, description, tools_config, auto_deploy)
|
||||
|
||||
logger.info(f"生成 Agent 项目: {agent_name}")
|
||||
logger.info(f" 包含工具: {len(tools_config)} 个")
|
||||
logger.info(f" 工具列表: {[t.get('name') for t in tools_config]}")
|
||||
logger.info(f" 自动部署: {auto_deploy}")
|
||||
logger.info(f" 包含回调功能: 是")
|
||||
|
||||
return files
|
||||
|
||||
def get_cicd_secrets_info(self) -> Dict[str, str]:
|
||||
|
||||
@@ -16,6 +16,8 @@ from database import (
|
||||
)
|
||||
from template_manager import template_manager
|
||||
from tool_generator_api import router as tool_generator_router
|
||||
from external_tool_api import router as external_tool_router
|
||||
from tool_storage import tool_storage
|
||||
import os
|
||||
|
||||
# 配置日志
|
||||
@@ -32,6 +34,9 @@ app = FastAPI(
|
||||
# 注册动态工具生成 Router
|
||||
app.include_router(tool_generator_router)
|
||||
|
||||
# 注册外部工具 API Router(符合 MCP-Server 规范)
|
||||
app.include_router(external_tool_router)
|
||||
|
||||
# 初始化K8s管理器
|
||||
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
|
||||
KUBECONFIG_PATH = os.getenv("KUBECONFIG_PATH", None) # 可选:指定kubeconfig路径
|
||||
@@ -207,6 +212,7 @@ class CreateAgentRequest(BaseModel):
|
||||
config: Dict = Field(default_factory=dict, description="配置信息")
|
||||
env: Optional[Dict[str, str]] = Field(default_factory=dict, description="环境变量")
|
||||
namespace: Optional[str] = Field(default=None, description="Kubernetes命名空间,默认使用环境变量NAMESPACE的值")
|
||||
tool_refs: Optional[List[str]] = Field(default=None, description="外部数据工具标识列表(符合MCP-Server规范)")
|
||||
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
@@ -224,6 +230,7 @@ class AgentResponse(BaseModel):
|
||||
host_ip: Optional[str] = None
|
||||
node_name: Optional[str] = None
|
||||
owner_info: Optional[Dict] = None
|
||||
tools_attached: Optional[int] = Field(default=0, description="附加的外部工具数量")
|
||||
|
||||
|
||||
class ResourceUsage(BaseModel):
|
||||
@@ -328,6 +335,36 @@ async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db
|
||||
config_data["env"] = request.env
|
||||
logger.info(f"环境变量: {list(request.env.keys())}")
|
||||
|
||||
# 处理 tool_refs(符合 MCP-Server 规范)
|
||||
attached_tools = []
|
||||
if request.tool_refs:
|
||||
logger.info(f"📦 处理外部工具引用: {request.tool_refs}")
|
||||
|
||||
# 验证所有工具存在
|
||||
missing_tools = []
|
||||
for ref in request.tool_refs:
|
||||
tool = tool_storage.get_tool(ref)
|
||||
if tool:
|
||||
attached_tools.append(tool)
|
||||
else:
|
||||
missing_tools.append(ref)
|
||||
|
||||
if missing_tools:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"以下工具不存在: {', '.join(missing_tools)}"
|
||||
)
|
||||
|
||||
# 将工具配置添加到 config
|
||||
config_data["tool_refs"] = request.tool_refs
|
||||
config_data["tools_count"] = len(attached_tools)
|
||||
|
||||
# 标记工具被使用
|
||||
for ref in request.tool_refs:
|
||||
tool_storage.mark_tool_in_use(ref, request.name)
|
||||
|
||||
logger.info(f"✅ 已附加 {len(attached_tools)} 个外部工具")
|
||||
|
||||
# 添加 user_id 标签
|
||||
user_id = config_data.get("user_id", "default")
|
||||
if "labels" not in config_data:
|
||||
@@ -516,6 +553,10 @@ async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db
|
||||
if "status" not in result:
|
||||
result["status"] = "Pending"
|
||||
|
||||
# 添加外部工具信息
|
||||
if attached_tools:
|
||||
result["tools_attached"] = len(attached_tools)
|
||||
|
||||
try:
|
||||
return AgentResponse(**result)
|
||||
except Exception as validation_error:
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
# Agent Manager DNS问题修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
在K8s上部署的Agent Manager服务,创建新agent后没有按照预期返回域名和外网地址。
|
||||
|
||||
## 问题诊断
|
||||
|
||||
### 1. 症状
|
||||
- 创建agent时返回外网IP地址(`external_ip`)
|
||||
- 但没有返回DNS域名信息(`domain`, `domain_url`)
|
||||
- 推荐访问地址使用的是IP地址而非域名
|
||||
|
||||
### 2. 根本原因
|
||||
通过查看agent-manager日志发现:
|
||||
```
|
||||
ERROR:k8s_manager:创建 DNS 记录失败: 403 Client Error: Forbidden
|
||||
ERROR:k8s_manager:错误详情: {"error":{"code":"AuthorizationFailed",
|
||||
"message":"The client 'c5ba26db-f180-425f-bac3-93708d853988' with object id
|
||||
'9dd70c60-bca4-478f-8cac-9faf9f171c93' does not have authorization to perform
|
||||
action 'Microsoft.Network/dnsZones/A/write' over scope
|
||||
'/subscriptions/45d7a360-af09-40fc-9afc-56dc475245ec/resourceGroups/taiji-ai-v0/
|
||||
providers/Microsoft.Network/dnsZones/taijiagnet.com/A/...' or the scope is invalid."}}
|
||||
```
|
||||
|
||||
**核心问题:** Azure Service Principal缺少DNS Zone的写权限。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 步骤1:为Service Principal分配DNS Zone Contributor角色
|
||||
|
||||
```bash
|
||||
# Azure配置
|
||||
AZURE_CLIENT_ID="c5ba26db-f180-425f-bac3-93708d853988"
|
||||
AZURE_SUBSCRIPTION_ID="45d7a360-af09-40fc-9afc-56dc475245ec"
|
||||
AZURE_RESOURCE_GROUP="taiji-ai-v0"
|
||||
AZURE_DNS_ZONE="taijiagnet.com"
|
||||
|
||||
# 构建DNS Zone资源ID
|
||||
DNS_ZONE_ID="/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$AZURE_RESOURCE_GROUP/providers/Microsoft.Network/dnsZones/$AZURE_DNS_ZONE"
|
||||
|
||||
# 分配DNS Zone Contributor角色
|
||||
az role assignment create \
|
||||
--assignee $AZURE_CLIENT_ID \
|
||||
--role "DNS Zone Contributor" \
|
||||
--scope $DNS_ZONE_ID
|
||||
```
|
||||
|
||||
### 步骤2:验证权限分配
|
||||
|
||||
```bash
|
||||
# 验证角色分配
|
||||
az role assignment list \
|
||||
--assignee $AZURE_CLIENT_ID \
|
||||
--scope $DNS_ZONE_ID \
|
||||
--output table
|
||||
```
|
||||
|
||||
输出应显示:
|
||||
```
|
||||
Principal Role Scope
|
||||
------------------------------------ -------------------- --------
|
||||
c5ba26db-f180-425f-bac3-93708d853988 DNS Zone Contributor /subscriptions/.../dnsZones/taijiagnet.com
|
||||
```
|
||||
|
||||
## 修复验证
|
||||
|
||||
### 测试1:创建新agent
|
||||
```bash
|
||||
curl -X POST http://20.212.121.126/agents \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "test-dns-fixed",
|
||||
"template": "echo_agent",
|
||||
"framework": "API",
|
||||
"config": {"user_id": "test-user"}
|
||||
}'
|
||||
```
|
||||
|
||||
### 结果(修复前)
|
||||
```json
|
||||
{
|
||||
"access_info": {
|
||||
"external_ip": "20.212.128.51",
|
||||
"ip_url": "http://20.212.128.51:80",
|
||||
"recommended": "http://20.212.128.51:80"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 结果(修复后)✅
|
||||
```json
|
||||
{
|
||||
"access_info": {
|
||||
"external_ip": "20.198.137.237",
|
||||
"ip_url": "http://20.198.137.237:80",
|
||||
"service_url": "http://10.0.48.107:80",
|
||||
"pod_url": "http://10.224.0.67:8000",
|
||||
"domain": "test-dns-fixed.taijiagnet.com",
|
||||
"domain_url": "http://test-dns-fixed.taijiagnet.com",
|
||||
"recommended": "http://test-dns-fixed.taijiagnet.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 测试2:验证DNS记录
|
||||
```bash
|
||||
# 查看DNS记录
|
||||
az network dns record-set a show \
|
||||
--resource-group taiji-ai-v0 \
|
||||
--zone-name taijiagnet.com \
|
||||
--name test-dns-fixed
|
||||
|
||||
# DNS解析测试
|
||||
nslookup test-dns-fixed.taijiagnet.com
|
||||
# Output: Address: 20.198.137.237
|
||||
|
||||
# 域名访问测试
|
||||
curl http://test-dns-fixed.taijiagnet.com/
|
||||
# Output: {"status":"healthy",...}
|
||||
```
|
||||
|
||||
### 测试3:查询agent状态
|
||||
```bash
|
||||
curl http://20.212.121.126/agents/test-dns-fixed/status | jq .access_info
|
||||
```
|
||||
|
||||
结果:
|
||||
```json
|
||||
{
|
||||
"external_ip": "20.198.137.237",
|
||||
"ip_url": "http://20.198.137.237:80",
|
||||
"domain": "test-dns-fixed.taijiagnet.com",
|
||||
"domain_url": "http://test-dns-fixed.taijiagnet.com",
|
||||
"recommended_url": "http://test-dns-fixed.taijiagnet.com",
|
||||
"service_name": "test-dns-fixed-service"
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Manager日志(修复后)
|
||||
|
||||
```
|
||||
INFO:app:收到创建Agent请求: test-dns-fixed, 模板: echo_agent
|
||||
INFO:k8s_manager:✅ 创建命名空间 agent-test-dns-fixed
|
||||
INFO:k8s_manager:✅ LoadBalancer Service test-dns-fixed-service 创建成功
|
||||
INFO:k8s_manager:✅ LoadBalancer IP 已分配: 20.198.137.237
|
||||
INFO:app:✅ LoadBalancer 外网 IP: 20.198.137.237
|
||||
INFO:app:创建 DNS 记录: test-dns-fixed.taijiagnet.com
|
||||
INFO:k8s_manager:✅ DNS 记录创建成功: test-dns-fixed.taijiagnet.com -> 20.198.137.237
|
||||
INFO:app:✅ DNS 记录: test-dns-fixed.taijiagnet.com -> 20.198.137.237
|
||||
INFO:app: - 推荐访问: http://test-dns-fixed.taijiagnet.com
|
||||
INFO:app:✅ Agent创建成功!
|
||||
INFO:app:✅ Agent信息已保存到数据库
|
||||
```
|
||||
|
||||
## 涉及的文件和组件
|
||||
|
||||
1. **Azure Service Principal** (c5ba26db-f180-425f-bac3-93708d853988)
|
||||
- 需要DNS Zone Contributor权限
|
||||
|
||||
2. **k8s_manager.py** - DNS记录创建逻辑
|
||||
- `create_dns_record()` - 使用Azure DNS API创建A记录
|
||||
- `wait_for_loadbalancer_ip()` - 等待LoadBalancer IP分配
|
||||
|
||||
3. **app.py** - Agent创建流程
|
||||
- 在创建LoadBalancer Service后自动创建DNS记录
|
||||
- 将域名信息保存到数据库
|
||||
|
||||
4. **database.py** - Agent模型
|
||||
- 存储`domain`, `domain_url`, `recommended_url`等字段
|
||||
|
||||
## 修复完成清单 ✅
|
||||
|
||||
- [x] 诊断DNS创建失败原因(Azure权限不足)
|
||||
- [x] 为Service Principal分配DNS Zone Contributor角色
|
||||
- [x] 测试DNS记录创建功能
|
||||
- [x] 验证agent创建返回完整的域名信息
|
||||
- [x] 验证agent状态查询返回完整的访问信息
|
||||
- [x] 验证DNS解析和域名访问正常
|
||||
- [x] 清理测试资源
|
||||
|
||||
## 后续建议
|
||||
|
||||
### 1. 文档更新
|
||||
更新部署文档,明确说明Service Principal需要的Azure权限:
|
||||
- AKS Cluster的Contributor角色
|
||||
- DNS Zone的DNS Zone Contributor角色
|
||||
|
||||
### 2. 自动化部署脚本
|
||||
在`k8s/deploy.sh`中添加权限检查和自动分配逻辑:
|
||||
```bash
|
||||
# 检查并分配DNS Zone权限
|
||||
check_dns_permissions() {
|
||||
echo "检查DNS Zone权限..."
|
||||
# 实现权限检查逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 监控和告警
|
||||
添加DNS创建失败的监控和告警机制:
|
||||
- 记录DNS创建失败次数
|
||||
- 当失败超过阈值时发送告警
|
||||
|
||||
### 4. 重试机制
|
||||
在`k8s_manager.py`中为DNS记录创建添加重试逻辑:
|
||||
```python
|
||||
def create_dns_record_with_retry(self, subdomain, ip_address, max_retries=3):
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
return self.create_dns_record(subdomain, ip_address)
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
logger.warning(f"DNS创建失败,重试 {attempt + 1}/{max_retries}")
|
||||
time.sleep(5)
|
||||
else:
|
||||
raise
|
||||
```
|
||||
|
||||
## 修复日期
|
||||
2026-01-28
|
||||
|
||||
## 修复人员
|
||||
AI Assistant (Claude Sonnet 4.5)
|
||||
+205
-422
@@ -1,7 +1,7 @@
|
||||
# 动态 Agent 生成器 API 文档
|
||||
|
||||
> **版本**: 2026-01-26 v1.0
|
||||
> **服务地址**: http://57.158.136.164
|
||||
> **版本**: 2026-01-29 v1.1
|
||||
> **服务地址**: http://20.212.121.126
|
||||
> **模板参考**: [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS)
|
||||
|
||||
---
|
||||
@@ -29,6 +29,12 @@
|
||||
│ │ AKS │ │
|
||||
│ │ (部署) │ │
|
||||
│ └──────────┘ │
|
||||
│ │ │
|
||||
│ ↓ │
|
||||
│ ┌──────────┐ │
|
||||
│ │Azure DNS │ │
|
||||
│ │ (域名) │ │
|
||||
│ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -41,33 +47,40 @@
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| 终结点 | `https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1` |
|
||||
| 模型 | `taiji/claude-sonnet-4-5` |
|
||||
| API Key | `sk-mPV5MVVVVvfGSkXA-ASQXQ` |
|
||||
| 终结点 | \`https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1\` |
|
||||
| 模型 | \`taiji/claude-sonnet-4-5\` |
|
||||
| API Key | \`sk-mPV5MVVVVvfGSkXA-ASQXQ\` |
|
||||
|
||||
### Gitee 配置
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| API URL | `http://gitee.ath.cx:3000/api/v1` |
|
||||
| 用户名 | `zhanggangyong` |
|
||||
| Owner | `xiaohei` |
|
||||
| API URL | \`http://gitee.ath.cx:3000/api/v1\` |
|
||||
| 用户名 | \`zhanggangyong\` |
|
||||
| Owner | \`xiaohei\` |
|
||||
| 模板仓库 | [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS) |
|
||||
|
||||
### ACR 配置
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| Registry | `agnettaiji.azurecr.io` |
|
||||
| Namespace | `ai-agents` |
|
||||
| Registry | \`agnettaiji.azurecr.io\` |
|
||||
| Namespace | \`ai-agents\` |
|
||||
|
||||
### Azure DNS 配置
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| DNS Zone | \`taijiagnet.com\` |
|
||||
| 资源组 | \`taiji-ai-v0\` |
|
||||
|
||||
### WAF 白名单 (重要!)
|
||||
|
||||
Gitee 服务器使用雷池 WAF 保护,需要将以下 IP 添加到白名单:
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
4.144.251.116 (AKS 集群出口 IP)
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
@@ -75,15 +88,15 @@ Gitee 服务器使用雷池 WAF 保护,需要将以下 IP 添加到白名单
|
||||
|
||||
| 序号 | 接口 | 方法 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | `/tools/generate` | POST | 生成单个外部数据工具 |
|
||||
| 2 | `/tools/generate-agent` | POST | 生成完整 Agent 并推送到 Gitee |
|
||||
| 3 | `/tools/{tool_ref_id}` | GET | 获取工具/Agent 信息 |
|
||||
| 4 | `/tools/{tool_ref_id}` | PUT | 更新工具配置 |
|
||||
| 5 | `/tools/{tool_ref_id}` | DELETE | 删除工具 |
|
||||
| 6 | `/tools/{tool_ref_id}/test` | POST | 测试工具连接 |
|
||||
| 7 | `/tools/{tool_ref_id}/build-status` | GET | 查询构建状态 |
|
||||
| 8 | `/tools/{tool_ref_id}/deploy` | POST | 部署 Agent |
|
||||
| 9 | `/tools/` | GET | 列出所有工具/Agent |
|
||||
| 1 | \`/tools/generate\` | POST | 生成单个外部数据工具 |
|
||||
| 2 | \`/tools/generate-agent\` | POST | 生成完整 Agent 并推送到 Gitee |
|
||||
| 3 | \`/tools/{tool_ref_id}\` | GET | 获取工具/Agent 信息 |
|
||||
| 4 | \`/tools/{tool_ref_id}\` | PUT | 更新工具配置 |
|
||||
| 5 | \`/tools/{tool_ref_id}\` | DELETE | 删除工具 |
|
||||
| 6 | \`/tools/{tool_ref_id}/test\` | POST | 测试工具连接 |
|
||||
| 7 | \`/tools/{tool_ref_id}/build-status\` | GET | 查询构建状态 |
|
||||
| 8 | \`/tools/{tool_ref_id}/deploy\` | POST | 部署 Agent |
|
||||
| 9 | \`/tools/\` | GET | 列出所有工具/Agent |
|
||||
|
||||
---
|
||||
|
||||
@@ -91,9 +104,9 @@ Gitee 服务器使用雷池 WAF 保护,需要将以下 IP 添加到白名单
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
POST /tools/generate
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 请求参数
|
||||
|
||||
@@ -114,39 +127,39 @@ POST /tools/generate
|
||||
|
||||
#### API Key 认证
|
||||
|
||||
```json
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "api_key",
|
||||
"key": "sk-xxxxxxxxxxxx",
|
||||
"in": "header",
|
||||
"name": "X-API-Key"
|
||||
}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
#### Bearer Token 认证
|
||||
|
||||
```json
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "bearer",
|
||||
"key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
#### Basic Auth 认证
|
||||
|
||||
```json
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "basic",
|
||||
"username": "admin",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://57.158.136.164/tools/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
\`\`\`bash
|
||||
curl -X POST http://20.212.121.126/tools/generate \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"name": "weather-query",
|
||||
"description": "查询城市天气",
|
||||
@@ -170,22 +183,78 @@ curl -X POST http://57.158.136.164/tools/generate \
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
\`\`\`json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tool_ref_id": "tool-weather-query-a49e00fe",
|
||||
"name": "weather-query",
|
||||
"description": "查询城市天气",
|
||||
"created_at": "2026-01-26T10:00:00.000000"
|
||||
"created_at": "2026-01-29T10:00:00.000000"
|
||||
},
|
||||
"message": "工具生成成功"
|
||||
}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 生成的工具代码示例
|
||||
|
||||
\`\`\`python
|
||||
"""
|
||||
工具: weather-query
|
||||
描述: 查询城市天气
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from typing import Optional, Any
|
||||
import httpx
|
||||
|
||||
|
||||
async def weather_query(city: str) -> str:
|
||||
"""
|
||||
查询城市天气
|
||||
|
||||
Args:
|
||||
city: 城市名称
|
||||
|
||||
Returns:
|
||||
API 响应结果 (JSON 格式)
|
||||
"""
|
||||
url = "https://api.weather.com/v1/current"
|
||||
headers = {}
|
||||
params = {"city": city}
|
||||
params["appid"] = os.getenv("TOOL_API_KEY", "")
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
response = await client.request(
|
||||
method="GET",
|
||||
url=url,
|
||||
headers=headers,
|
||||
params={k: v for k, v in params.items() if v is not None}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"data": response.json()
|
||||
}, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
return json.dumps({
|
||||
"success": False,
|
||||
"status_code": response.status_code,
|
||||
"error": response.text[:500]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
@@ -193,9 +262,9 @@ curl -X POST http://57.158.136.164/tools/generate \
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
POST /tools/generate-agent
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 功能描述
|
||||
|
||||
@@ -203,6 +272,7 @@ POST /tools/generate-agent
|
||||
2. 创建 Gitee 仓库并推送代码
|
||||
3. 包含完整 CI/CD 配置(参考 [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS))
|
||||
4. 自动触发构建和部署
|
||||
5. 自动创建 Azure DNS 记录
|
||||
|
||||
### 请求参数
|
||||
|
||||
@@ -213,13 +283,13 @@ POST /tools/generate-agent
|
||||
| tools | array | ✅ | 工具配置列表 |
|
||||
| user_id | string | ✅ | 用户 ID |
|
||||
| tenant_id | string | ❌ | 租户 ID |
|
||||
| auto_deploy | boolean | ❌ | 是否自动部署到 AKS(默认 true) |
|
||||
| auto_deploy | boolean | ❌ | 是否自动部署到 AKS(默认 false) |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://57.158.136.164/tools/generate-agent \
|
||||
-H "Content-Type: application/json" \
|
||||
\`\`\`bash
|
||||
curl -X POST http://20.212.121.126/tools/generate-agent \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"agent_name": "weather-agent",
|
||||
"description": "天气查询智能 Agent,支持获取全球城市天气信息",
|
||||
@@ -254,31 +324,14 @@ curl -X POST http://57.158.136.164/tools/generate-agent \
|
||||
}
|
||||
},
|
||||
"timeout": 30
|
||||
},
|
||||
{
|
||||
"name": "get_forecast",
|
||||
"description": "获取城市未来天气预报",
|
||||
"url": "https://api.openweathermap.org/data/2.5/forecast",
|
||||
"method": "GET",
|
||||
"user_id": "user-001",
|
||||
"request_params": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"q": {
|
||||
"type": "string",
|
||||
"description": "城市名称",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
\`\`\`json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
@@ -287,256 +340,78 @@ curl -X POST http://57.158.136.164/tools/generate-agent \
|
||||
"repo_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-weather-agent-ac3c11",
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest",
|
||||
"status": "building",
|
||||
"files_pushed": 7,
|
||||
"tools_count": 2
|
||||
"files_pushed": 9,
|
||||
"tools_count": 2,
|
||||
"expected_domain": "agent-weather-agent-ac3c11.taijiagnet.com",
|
||||
"expected_namespace": "agent-agent-weather-agent-ac3c11"
|
||||
},
|
||||
"message": "Agent 项目已创建并推送到 Gitee,CI/CD 正在构建中"
|
||||
"message": "Agent 项目已创建并推送到 Gitee,CI/CD 正在构建中。部署后访问: http://agent-weather-agent-ac3c11.taijiagnet.com"
|
||||
}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 生成的项目结构
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
agent-weather-agent-{id}/
|
||||
├── .gitea/workflows/
|
||||
│ └── ci-cd.yaml # CI/CD 配置(构建+部署)
|
||||
├── tools/
|
||||
│ └── ci-cd.yaml # CI/CD 配置(构建+部署+DNS)
|
||||
├── src/
|
||||
│ ├── __init__.py
|
||||
│ ├── get_weather.py # 工具1: 天气查询
|
||||
│ └── get_forecast.py # 工具2: 天气预报
|
||||
├── main.py # FastAPI 主程序
|
||||
├── Dockerfile # Docker 构建文件
|
||||
├── requirements.txt # Python 依赖
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
│ └── server/
|
||||
│ ├── __init__.py
|
||||
│ ├── mcp_server.py # MCP 服务器 + 工具定义
|
||||
│ └── api_server.py # FastAPI HTTP 服务器
|
||||
├── run_api_server.py # 入口文件
|
||||
├── Dockerfile # Docker 构建文件
|
||||
├── requirements.txt # Python 依赖
|
||||
└── README.md # 说明文档
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
## 3. 获取工具/Agent 信息
|
||||
## 3-9. 其他接口
|
||||
|
||||
### 接口
|
||||
### 3. 获取工具/Agent 信息
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
GET /tools/{tool_ref_id}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 响应示例
|
||||
### 4. 更新工具配置
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "agent-agent-weather-agent-ac3c11",
|
||||
"name": "weather-agent",
|
||||
"description": "天气查询智能 Agent",
|
||||
"status": "building",
|
||||
"created_at": "2026-01-26T10:00:00.000000",
|
||||
"repo_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-weather-agent-ac3c11",
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 更新工具配置
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
PUT /tools/{tool_ref_id}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 请求参数
|
||||
### 5. 删除工具
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| description | string | 工具描述 |
|
||||
| url | string | API URL |
|
||||
| method | string | HTTP 方法 |
|
||||
| auth | object | 认证配置 |
|
||||
| request_params | object | 请求参数 |
|
||||
| timeout | integer | 超时时间 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 删除工具
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
DELETE /tools/{tool_ref_id}?delete_repo=false
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 参数
|
||||
### 6. 测试工具连接
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| delete_repo | boolean | 是否同时删除 Gitee 仓库 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试工具连接
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
POST /tools/{tool_ref_id}/test
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 请求参数
|
||||
### 7. 查询构建状态
|
||||
|
||||
```json
|
||||
{
|
||||
"test_params": {
|
||||
"q": "Beijing"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status_code": 200,
|
||||
"response_time_ms": 156.5,
|
||||
"content_type": "application/json",
|
||||
"response_preview": "{\"coord\":{\"lon\":116.4074,\"lat\":39.9042}...}"
|
||||
},
|
||||
"message": "工具连接测试成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 查询构建状态
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
GET /tools/{tool_ref_id}/build-status
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 功能描述
|
||||
### 8. 部署 Agent
|
||||
|
||||
1. 查询 Gitee Action 运行状态
|
||||
2. 查询 ACR 镜像是否已构建完成
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tool_ref_id": "agent-agent-weather-agent-ac3c11",
|
||||
"repo_name": "agent-weather-agent-ac3c11",
|
||||
"overall_status": "ready",
|
||||
"gitee_action": {
|
||||
"status": "completed",
|
||||
"conclusion": "success",
|
||||
"run_id": 12345,
|
||||
"html_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-weather-agent-ac3c11/actions/runs/12345"
|
||||
},
|
||||
"acr_image": {
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest",
|
||||
"exists": true,
|
||||
"digest": "sha256:abc123...",
|
||||
"created_at": "2026-01-26T10:05:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态说明
|
||||
|
||||
| 状态 | 说明 |
|
||||
|------|------|
|
||||
| `building` | 正在构建中 |
|
||||
| `ready` | 镜像已就绪,可以部署 |
|
||||
| `build_failed` | 构建失败 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 部署 Agent
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
POST /tools/{tool_ref_id}/deploy
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 前提条件
|
||||
### 9. 列出所有工具/Agent
|
||||
|
||||
- ACR 中已存在镜像(构建状态为 `ready`)
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tool_ref_id": "agent-agent-weather-agent-ac3c11",
|
||||
"template_name": "agent_weather_agent_ac3c11",
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-weather-agent-ac3c11:latest",
|
||||
"status": "deployed",
|
||||
"message": "模板 agent_weather_agent_ac3c11 已创建,可通过 POST /agents 创建实例"
|
||||
},
|
||||
"message": "Agent 模板已创建,可以开始部署实例"
|
||||
}
|
||||
```
|
||||
|
||||
### 部署实例
|
||||
|
||||
模板创建后,使用标准的 Agent 创建接口部署实例:
|
||||
|
||||
```bash
|
||||
curl -X POST http://57.158.136.164/agents \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-weather-instance",
|
||||
"template": "agent_weather_agent_ac3c11",
|
||||
"config": {}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 列出所有工具/Agent
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
\`\`\`
|
||||
GET /tools/?user_id=user-001
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tools": [
|
||||
{
|
||||
"id": "agent-agent-weather-agent-ac3c11",
|
||||
"name": "weather-agent",
|
||||
"type": "agent",
|
||||
"status": "ready",
|
||||
"created_at": "2026-01-26T10:00:00.000000"
|
||||
},
|
||||
{
|
||||
"id": "tool-weather-query-a49e00fe",
|
||||
"name": "weather-query",
|
||||
"type": "tool",
|
||||
"status": "created",
|
||||
"created_at": "2026-01-26T09:00:00.000000"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
@@ -557,10 +432,9 @@ GET /tools/?user_id=user-001
|
||||
|
||||
### 步骤 1: 创建 Agent
|
||||
|
||||
```bash
|
||||
# 发送工具定义,创建 Agent 项目
|
||||
curl -X POST http://57.158.136.164/tools/generate-agent \
|
||||
-H "Content-Type: application/json" \
|
||||
\`\`\`bash
|
||||
curl -X POST http://20.212.121.126/tools/generate-agent \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"agent_name": "my-custom-agent",
|
||||
"description": "我的自定义 Agent",
|
||||
@@ -568,165 +442,73 @@ curl -X POST http://57.158.136.164/tools/generate-agent \
|
||||
"auto_deploy": true,
|
||||
"tools": [...]
|
||||
}'
|
||||
|
||||
# 返回 agent_ref_id
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
### 步骤 2: 等待构建完成
|
||||
|
||||
```bash
|
||||
# 轮询查询构建状态
|
||||
curl http://57.158.136.164/tools/{agent_ref_id}/build-status
|
||||
\`\`\`bash
|
||||
curl http://20.212.121.126/tools/{agent_ref_id}/build-status
|
||||
\`\`\`
|
||||
|
||||
# 等待 overall_status 变为 "ready"
|
||||
```
|
||||
### 步骤 3: 访问 Agent
|
||||
|
||||
### 步骤 3: 部署 Agent(如果 auto_deploy=false)
|
||||
\`\`\`bash
|
||||
# 通过域名访问
|
||||
curl http://{agent-name}.taijiagnet.com/
|
||||
|
||||
```bash
|
||||
# 手动部署
|
||||
curl -X POST http://57.158.136.164/tools/{agent_ref_id}/deploy
|
||||
```
|
||||
# 健康检查
|
||||
curl http://{agent-name}.taijiagnet.com/health
|
||||
|
||||
### 步骤 4: 创建 Agent 实例
|
||||
# 调用工具
|
||||
curl -X POST http://{agent-name}.taijiagnet.com/tools/call \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"tool_name": "get_weather", "parameters": {"q": "Beijing"}}'
|
||||
|
||||
```bash
|
||||
# 使用生成的模板创建实例
|
||||
curl -X POST http://57.158.136.164/agents \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-instance",
|
||||
"template": "{template_name}",
|
||||
"config": {}
|
||||
}'
|
||||
```
|
||||
# MCP 调用
|
||||
curl -X POST http://{agent-name}.taijiagnet.com/mcp \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
## CI/CD 配置说明
|
||||
## Agent 端点说明
|
||||
|
||||
生成的 CI/CD 配置参考 [cicd-AKS](http://gitee.ath.cx:3000/xiaohei/cicd-AKS),包含:
|
||||
生成的 Agent 提供以下端点:
|
||||
|
||||
1. **构建 Docker 镜像** (ARM64 架构)
|
||||
2. **推送到 ACR**
|
||||
3. **部署到 AKS**(如果 `auto_deploy=true`)
|
||||
### REST API
|
||||
|
||||
### 生成的 CI/CD 配置示例
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| \`/\` | GET | 服务信息 |
|
||||
| \`/health\` | GET | 健康检查 |
|
||||
| \`/tools\` | GET | 列出可用工具 |
|
||||
| \`/tools/call\` | POST | 调用工具 |
|
||||
|
||||
```yaml
|
||||
name: CI-CD-AKS
|
||||
### MCP 端点
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to ACR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: agnettaiji.azurecr.io
|
||||
username: ${{ secrets.ACR_USERNAME }}
|
||||
password: ${{ secrets.ACR_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: agnettaiji.azurecr.io/ai-agents/{agent-name}:latest
|
||||
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
creds: |
|
||||
{
|
||||
"clientId": "${{ secrets.AZ_CLIENT_ID }}",
|
||||
"clientSecret": "${{ secrets.AZ_CLIENT_SECRET }}",
|
||||
"subscriptionId": "${{ secrets.AZ_SUBSCRIPTION_ID }}",
|
||||
"tenantId": "${{ secrets.AZ_TENANT_ID }}"
|
||||
}
|
||||
|
||||
- name: Get AKS credentials
|
||||
run: |
|
||||
az aks get-credentials --resource-group ${{ secrets.AZ_RG }} \
|
||||
--name ${{ secrets.AZ_AKS }} --overwrite-existing
|
||||
|
||||
- name: Deploy to AKS
|
||||
run: |
|
||||
kubectl set image deployment/{agent-name} \
|
||||
{agent-name}=agnettaiji.azurecr.io/ai-agents/{agent-name}:latest \
|
||||
-n ai-agents || echo "Deployment will be created by agent-manager"
|
||||
```
|
||||
|
||||
### 必需的 Gitee Secrets
|
||||
|
||||
在 Gitee 仓库设置中配置以下 Secrets:
|
||||
|
||||
| Secret 名称 | 说明 | 值 |
|
||||
|-------------|------|-----|
|
||||
| `ACR_LOGIN_SERVER` | ACR 登录服务器 | `agnettaiji.azurecr.io` |
|
||||
| `ACR_USERNAME` | ACR 用户名 | `agnettaiji` |
|
||||
| `ACR_PASSWORD` | ACR 密码 | `hDpX5t34N5ZmnKdtqyjYL5co/SnXJrmD20CRpGpWaG+ACRCw2wGM` |
|
||||
| `AZ_CLIENT_ID` | Azure 服务主体 Client ID | `fb306798-2cfe-4ac9-ba48-eab7bc71bcfe` |
|
||||
| `AZ_CLIENT_SECRET` | Azure 服务主体密钥 | `cVK8Q~xlfBwm2_t2TC24yrTukWV4F3G~eIjBBa0D` |
|
||||
| `AZ_TENANT_ID` | Azure 租户 ID | `263c3ff6-1be5-4141-8308-b188464fb297` |
|
||||
| `AZ_SUBSCRIPTION_ID` | Azure 订阅 ID | `c6c47e4c-f5f4-49f8-b26f-7728862c17d6` |
|
||||
| `AZ_RG` | AKS 资源组名称 | `taiji-ai-pda` |
|
||||
| `AZ_AKS` | AKS 集群名称 | `taiji-ai-pda` |
|
||||
|
||||
### Gitee Runner 配置
|
||||
|
||||
Runner 注册 Token: `1A4jRxECdtycHAuzDHcICNeVmLaWrZ9FvwV2vYY2`
|
||||
|
||||
注册命令:
|
||||
```bash
|
||||
act_runner register --no-interactive \
|
||||
--instance "http://gitee.ath.cx:3000" \
|
||||
--token "1A4jRxECdtycHAuzDHcICNeVmLaWrZ9FvwV2vYY2" \
|
||||
--name "your-runner-name" \
|
||||
--labels "self-hosted,linux,arm64"
|
||||
```
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| \`/mcp\` | POST | MCP HTTP 端点 |
|
||||
| \`/mcp/sse\` | GET | MCP SSE 端点 |
|
||||
| \`/mcp/sse\` | POST | MCP SSE POST 端点 |
|
||||
|
||||
---
|
||||
|
||||
## 环境变量配置
|
||||
## CI/CD Secrets 配置
|
||||
|
||||
Agent Manager 需要以下环境变量:
|
||||
|
||||
### ConfigMap (agent-manager-config)
|
||||
|
||||
```yaml
|
||||
GITEE_API_URL: "http://gitee.ath.cx:3000/api/v1"
|
||||
GITEE_BASE_URL: "http://gitee.ath.cx:3000"
|
||||
GITEE_OWNER: "xiaohei"
|
||||
GITEE_TEMPLATE_REPO: "cicd-AKS"
|
||||
ACR_REGISTRY: "agnettaiji.azurecr.io"
|
||||
ACR_NAMESPACE: "ai-agents"
|
||||
LLM_BASE_URL: "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1"
|
||||
LLM_MODEL: "taiji/claude-sonnet-4-5"
|
||||
```
|
||||
|
||||
### Secret (agent-manager-secret)
|
||||
|
||||
```yaml
|
||||
GITEE_TOKEN: <base64 encoded token>
|
||||
GITEE_USERNAME: <base64 encoded: zhanggangyong>
|
||||
GITEE_PASSWORD: <base64 encoded password>
|
||||
LLM_API_KEY: <base64 encoded: sk-mPV5MVVVVvfGSkXA-ASQXQ>
|
||||
```
|
||||
| Secret 名称 | 说明 |
|
||||
|-------------|------|
|
||||
| \`ACR_LOGIN_SERVER\` | ACR 登录服务器 |
|
||||
| \`ACR_USERNAME\` | ACR 用户名 |
|
||||
| \`ACR_PASSWORD\` | ACR 密码 |
|
||||
| \`AZ_CLIENT_ID\` | Azure 服务主体 Client ID |
|
||||
| \`AZ_CLIENT_SECRET\` | Azure 服务主体密钥 |
|
||||
| \`AZ_TENANT_ID\` | Azure 租户 ID |
|
||||
| \`AZ_SUBSCRIPTION_ID\` | Azure 订阅 ID |
|
||||
| \`AZ_RG\` | AKS 资源组名称 |
|
||||
| \`AZ_AKS\` | AKS 集群名称 |
|
||||
| \`AZURE_DNS_ZONE\` | DNS Zone 名称 |
|
||||
|
||||
---
|
||||
|
||||
@@ -735,3 +517,4 @@ LLM_API_KEY: <base64 encoded: sk-mPV5MVVVVvfGSkXA-ASQXQ>
|
||||
| 日期 | 版本 | 更新内容 |
|
||||
|------|------|----------|
|
||||
| 2026-01-26 | v1.0 | 初始版本 |
|
||||
| 2026-01-29 | v1.1 | 更新服务地址 (20.212.121.126);添加 DNS 配置说明;添加 generate_tool_code 生成的代码示例;更新项目结构为 src/server/ 目录;添加 expected_domain/expected_namespace 响应字段;添加 Agent 端点说明;简化 Secrets 配置表格 |
|
||||
|
||||
@@ -0,0 +1,741 @@
|
||||
# 外部工具 API 文档
|
||||
|
||||
> **版本**: 2026-01-29 v2.0
|
||||
> **服务地址**: http://20.212.121.126
|
||||
> **规范参考**: [Agent-Manager外部工具接口规范](http://gitee.ath.cx:3000/xiaohei/taiji-AI-PAD/src/branch/feature/chenchen/Docs/Agent-Manager%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E6%8E%A5%E5%8F%A3%E8%A7%84%E8%8C%83.md)
|
||||
|
||||
---
|
||||
|
||||
## 📊 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 系统交互流程 │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 前端 │ ───→ │ MCP-Server │ ───→ │ Agent Manager │ │
|
||||
│ │ 用户界面 │ │ (调用方) │ │ (本API规范) │ │
|
||||
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ │ │ │
|
||||
│ ↓ ↓ │
|
||||
│ ┌─────────────┐ ┌─────────────────┐ │
|
||||
│ │ PostgreSQL │ │ 工具文件存储 │ │
|
||||
│ │ (基本信息) │ │ AKS 部署 │ │
|
||||
│ └─────────────┘ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ 计费回调系统 │ │
|
||||
│ │ (运行时长记录) │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 职责划分
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| **MCP-Server** | 接收前端请求、存储工具基本信息和 tool_ref_id、调用 Agent Manager 接口 |
|
||||
| **Agent Manager** | 生成 Pydantic 工具代码文件、存储完整配置(含敏感信息)、部署 Agent 到 AKS、集成计费回调 |
|
||||
| **计费系统** | 接收 Agent 运行时长回调、记录使用的工具、计算费用 |
|
||||
|
||||
---
|
||||
|
||||
## 🆕 v2.0 新增功能
|
||||
|
||||
### 1. 回调功能(计费)
|
||||
|
||||
每个生成的 Agent 自动包含回调模块,用于向计费系统发送使用记录:
|
||||
|
||||
- 记录 Agent 运行时长
|
||||
- 记录使用的工具列表
|
||||
- 支持请求级别的计费追踪
|
||||
|
||||
### 2. 多工具支持
|
||||
|
||||
每个 Agent 可以包含**多个工具**,在创建时通过 `tool_refs` 数组指定。
|
||||
|
||||
### 3. 新增状态查询 API
|
||||
|
||||
- `GET /agents/{agent_ref_id}/build-status` - 查询 CI/CD 构建状态
|
||||
- `GET /agents/{agent_ref_id}/deployment-info` - 查询部署详情
|
||||
|
||||
### 4. 批量工具调用
|
||||
|
||||
- `POST /tools/batch-call` - 批量调用多个工具(统一计费)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 通用规范
|
||||
|
||||
### 基础路径
|
||||
|
||||
```
|
||||
http://20.212.121.126/external-tools
|
||||
```
|
||||
|
||||
### 请求头
|
||||
|
||||
```http
|
||||
Content-Type: application/json
|
||||
X-User-ID: <user_id> # 可选,用于计费
|
||||
```
|
||||
|
||||
### 响应格式
|
||||
|
||||
#### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"message": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "error_code",
|
||||
"message": "错误描述"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📑 接口列表
|
||||
|
||||
| 序号 | 接口 | 方法 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | `/external-tools/generate` | POST | 生成外部数据工具 |
|
||||
| 2 | `/external-tools/{tool_ref_id}` | GET | 获取工具详情 |
|
||||
| 3 | `/external-tools/{tool_ref_id}` | PUT | 更新外部数据工具 |
|
||||
| 4 | `/external-tools/{tool_ref_id}` | DELETE | 删除外部数据工具 |
|
||||
| 5 | `/external-tools/{tool_ref_id}/test` | POST | 测试工具连接 |
|
||||
| 6 | `/external-tools/{tool_ref_id}/code` | GET | 获取生成的代码 |
|
||||
| 7 | `/external-tools/` | GET | 列出所有工具 |
|
||||
| 8 | `/external-tools/agents/create-with-tools` | POST | 创建带工具的 Agent |
|
||||
| 9 | `/external-tools/agents/{agent_ref_id}/build-status` | GET | 🆕 查询构建状态 |
|
||||
| 10 | `/external-tools/agents/{agent_ref_id}/deployment-info` | GET | 🆕 查询部署信息 |
|
||||
| 11 | `/agents` | POST | 创建 Agent(支持 tool_refs 字段) |
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ 生成外部数据工具
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
POST /external-tools/generate
|
||||
```
|
||||
|
||||
### 功能描述
|
||||
|
||||
MCP-Server 将用户配置的工具信息发送给 Agent Manager,Agent Manager 需要:
|
||||
1. 验证配置格式
|
||||
2. 根据配置生成 Pydantic AI 工具代码文件
|
||||
3. 存储工具代码文件和完整配置(包含敏感信息如 API Key)
|
||||
4. 返回唯一的 `tool_ref_id` 供后续引用
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| name | string | ✅ | 工具名称(1-100 字符,将用于生成 Python 函数名) |
|
||||
| description | string | ✅ | 工具描述(将作为工具的 docstring) |
|
||||
| url | string | ✅ | API 端点 URL |
|
||||
| method | string | ✅ | HTTP 方法:GET/POST/PUT/DELETE/PATCH |
|
||||
| user_id | string | ✅ | 用户 ID(UUID 格式) |
|
||||
| tenant_id | string | ❌ | 租户 ID(UUID 格式) |
|
||||
| headers | object | ❌ | 自定义请求头 |
|
||||
| auth | object | ❌ | 认证配置(详见下方) |
|
||||
| request_params | object | ❌ | URL 查询参数定义(JSON Schema 格式) |
|
||||
| request_body | object | ❌ | 请求体定义(JSON Schema 格式) |
|
||||
| response_mapping | object | ❌ | 响应字段映射 |
|
||||
| timeout | integer | ❌ | 超时时间(秒),默认 30 |
|
||||
| retry | object | ❌ | 重试配置 |
|
||||
|
||||
### 认证配置 (auth) 结构
|
||||
|
||||
#### API Key 认证
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "api_key",
|
||||
"key": "sk-xxxxxxxxxxxx",
|
||||
"in": "header", // 位置: header / query
|
||||
"name": "X-API-Key" // 参数名
|
||||
}
|
||||
```
|
||||
|
||||
#### Bearer Token 认证
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "bearer",
|
||||
"key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Basic Auth 认证
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "basic",
|
||||
"username": "admin",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://20.212.121.126/external-tools/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "weather-query",
|
||||
"description": "查询指定城市的天气信息",
|
||||
"url": "https://api.weather.com/v1/current",
|
||||
"method": "GET",
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"auth": {
|
||||
"type": "api_key",
|
||||
"key": "sk-xxx",
|
||||
"in": "query",
|
||||
"name": "apikey"
|
||||
},
|
||||
"request_params": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "城市名称"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tool_ref_id": "tool-weather-query-7ff71b5e",
|
||||
"name": "weather-query",
|
||||
"description": "查询指定城市的天气信息",
|
||||
"created_at": "2026-01-29T11:07:32.538533"
|
||||
},
|
||||
"message": "工具生成成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ 更新外部数据工具
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
PUT /external-tools/{tool_ref_id}
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| description | string | ❌ | 工具描述 |
|
||||
| url | string | ❌ | API URL |
|
||||
| method | string | ❌ | HTTP 方法 |
|
||||
| headers | object | ❌ | 自定义请求头 |
|
||||
| auth | object | ❌ | 认证配置 |
|
||||
| request_params | object | ❌ | 请求参数定义 |
|
||||
| request_body | object | ❌ | 请求体定义 |
|
||||
| timeout | integer | ❌ | 超时时间 |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X PUT http://20.212.121.126/external-tools/tool-weather-query-7ff71b5e \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"description": "更新后的天气查询工具描述",
|
||||
"timeout": 60
|
||||
}'
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tool_ref_id": "tool-weather-query-7ff71b5e",
|
||||
"name": "weather-query",
|
||||
"updated_at": "2026-01-29T11:09:12.322545"
|
||||
},
|
||||
"message": "工具更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ 删除外部数据工具
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
DELETE /external-tools/{tool_ref_id}
|
||||
```
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://20.212.121.126/external-tools/tool-weather-query-7ff71b5e
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"tool_ref_id": "tool-weather-query-7ff71b5e"
|
||||
},
|
||||
"message": "工具删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
- 如果工具正在被 Agent 使用,删除将失败并返回 `tool_in_use` 错误
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ 测试工具连接
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
POST /external-tools/{tool_ref_id}/test
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| test_params | object | ❌ | 测试参数(URL 查询参数) |
|
||||
| test_body | object | ❌ | 测试请求体 |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://20.212.121.126/external-tools/tool-weather-query-7ff71b5e/test \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"test_params": {"city": "Beijing"}}'
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status_code": 200,
|
||||
"response_time_ms": 1058.84,
|
||||
"content_type": "application/json",
|
||||
"response_preview": "{...}"
|
||||
},
|
||||
"message": "连接测试成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ 创建带有外部工具的 Agent(支持多工具)
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
POST /external-tools/agents/create-with-tools
|
||||
```
|
||||
|
||||
### 功能描述
|
||||
|
||||
创建带有外部工具的 Agent,自动:
|
||||
1. 加载对应的工具代码文件(**支持多个工具**)
|
||||
2. 将工具集成到 Agent 中
|
||||
3. 生成完整项目并推送到 Gitee
|
||||
4. 触发 CI/CD 构建和部署
|
||||
5. **集成计费回调功能**
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| name | string | ✅ | Agent 名称(1-63 字符,符合 K8s 命名规范) |
|
||||
| template | string | ✅ | Agent 模板名称 |
|
||||
| tool_refs | string[] | ✅ | 外部数据工具标识列表(**支持多个**) |
|
||||
| config | object | ❌ | 资源配置 |
|
||||
| env | object | ❌ | 环境变量 |
|
||||
|
||||
### 请求示例(多工具)
|
||||
|
||||
```bash
|
||||
curl -X POST http://20.212.121.126/external-tools/agents/create-with-tools \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "multi-tool-agent",
|
||||
"template": "custom_agent",
|
||||
"tool_refs": [
|
||||
"tool-search-api-60e5c2a1",
|
||||
"tool-weather-api-8e08be59",
|
||||
"tool-translate-api-82e0f9bd"
|
||||
],
|
||||
"config": {
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"name": "multi-tool-agent",
|
||||
"agent_ref_id": "agent-agent-multi-tool-agent-ad2b92",
|
||||
"namespace": "agent-agent-multi-tool-agent-ad2b92",
|
||||
"status": "Building",
|
||||
"created_at": "2026-01-29T11:45:30.123456",
|
||||
"template": "custom_agent",
|
||||
"service_port": 8000,
|
||||
"access_info": {
|
||||
"domain": "agent-multi-tool-agent-ad2b92.taijiagnet.com",
|
||||
"domain_url": "http://agent-multi-tool-agent-ad2b92.taijiagnet.com",
|
||||
"ip_url": "pending"
|
||||
},
|
||||
"tools_attached": 3,
|
||||
"repo_url": "http://gitee.ath.cx:3000/zhanggangyong/agent-multi-tool-agent-ad2b92",
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-multi-tool-agent-ad2b92:latest",
|
||||
"status_query_url": "/external-tools/agents/agent-agent-multi-tool-agent-ad2b92/build-status",
|
||||
"deployment_info_url": "/external-tools/agents/agent-agent-multi-tool-agent-ad2b92/deployment-info"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ 🆕 查询 CI/CD 构建状态
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
GET /external-tools/agents/{agent_ref_id}/build-status
|
||||
```
|
||||
|
||||
### 功能描述
|
||||
|
||||
查询 Agent 的 CI/CD 构建状态,包括:
|
||||
- Gitee Action 运行状态
|
||||
- ACR 镜像是否已构建
|
||||
- 整体构建状态
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"agent_ref_id": "agent-agent-multi-tool-agent-ad2b92",
|
||||
"repo_name": "agent-multi-tool-agent-ad2b92",
|
||||
"overall_status": "ready",
|
||||
"gitee_action": {
|
||||
"status": "completed",
|
||||
"conclusion": "success",
|
||||
"run_id": 123,
|
||||
"html_url": "http://gitee.ath.cx:3000/.../actions/runs/123"
|
||||
},
|
||||
"acr_image": {
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-multi-tool-agent-ad2b92:latest",
|
||||
"exists": true,
|
||||
"tags": ["latest", "abc123"]
|
||||
},
|
||||
"access_info": {
|
||||
"expected_domain": "agent-multi-tool-agent-ad2b92.taijiagnet.com",
|
||||
"expected_url": "http://agent-multi-tool-agent-ad2b92.taijiagnet.com",
|
||||
"expected_namespace": "agent-agent-multi-tool-agent-ad2b92"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态说明
|
||||
|
||||
| overall_status | 含义 |
|
||||
|----------------|------|
|
||||
| `pending` | 等待 CI/CD 开始 |
|
||||
| `building` | CI/CD 正在构建 |
|
||||
| `ready` | 镜像已构建,等待部署 |
|
||||
| `deployed` | 已部署到 K8s |
|
||||
| `build_failed` | 构建失败 |
|
||||
| `unknown` | 状态未知 |
|
||||
|
||||
---
|
||||
|
||||
## 7️⃣ 🆕 查询部署详情
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
GET /external-tools/agents/{agent_ref_id}/deployment-info
|
||||
```
|
||||
|
||||
### 功能描述
|
||||
|
||||
查询 Agent 部署后的详细信息,包括:
|
||||
- K8s 部署状态
|
||||
- Pod 运行状态
|
||||
- 服务端点信息
|
||||
- DNS 域名信息
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"agent_ref_id": "agent-agent-multi-tool-agent-ad2b92",
|
||||
"repo_name": "agent-multi-tool-agent-ad2b92",
|
||||
"namespace": "agent-agent-multi-tool-agent-ad2b92",
|
||||
"k8s_status": {
|
||||
"namespace_exists": true,
|
||||
"namespace_status": "Active",
|
||||
"pod_name": "agent-multi-tool-agent-ad2b92-xxx",
|
||||
"pod_status": "Running",
|
||||
"pod_ip": "10.224.0.50",
|
||||
"node_name": "aks-nodepool1-xxx",
|
||||
"status": "running"
|
||||
},
|
||||
"access_info": {
|
||||
"service_name": "agent-multi-tool-agent-ad2b92-service",
|
||||
"cluster_ip": "10.0.100.50",
|
||||
"external_ip": "20.x.x.x",
|
||||
"ip_url": "http://20.x.x.x",
|
||||
"domain": "agent-multi-tool-agent-ad2b92.taijiagnet.com",
|
||||
"domain_url": "http://agent-multi-tool-agent-ad2b92.taijiagnet.com",
|
||||
"recommended_url": "http://agent-multi-tool-agent-ad2b92.taijiagnet.com"
|
||||
},
|
||||
"image_info": {
|
||||
"image_name": "agnettaiji.azurecr.io/ai-agents/agent-multi-tool-agent-ad2b92:latest",
|
||||
"registry": "agnettaiji.azurecr.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8️⃣ 原有 /agents 接口 - 支持 tool_refs
|
||||
|
||||
### 接口
|
||||
|
||||
```
|
||||
POST /agents
|
||||
```
|
||||
|
||||
### 新增参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| tool_refs | string[] | ❌ | 外部数据工具标识列表(支持多个) |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://20.212.121.126/agents \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "test-tools-agent",
|
||||
"template": "echo_agent",
|
||||
"framework": "API",
|
||||
"config": {
|
||||
"user_id": "test-user"
|
||||
},
|
||||
"tool_refs": ["tool-weather-query-7ff71b5e", "tool-stock-api-abc123"]
|
||||
}'
|
||||
```
|
||||
|
||||
### 响应新增字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| tools_attached | integer | 附加的外部工具数量 |
|
||||
|
||||
---
|
||||
|
||||
## 🔔 回调功能(计费)
|
||||
|
||||
### 概述
|
||||
|
||||
每个生成的 Agent 自动集成回调功能,用于向计费系统发送使用记录。
|
||||
|
||||
### 生成的文件结构
|
||||
|
||||
```
|
||||
src/server/
|
||||
├── mcp_server.py # MCP 服务器(工具定义)
|
||||
├── api_server.py # HTTP API(集成回调)
|
||||
└── agent_callback_utils.py # 回调工具模块
|
||||
```
|
||||
|
||||
### 回调数据格式
|
||||
|
||||
Agent 在处理请求时会自动发送以下数据到计费系统:
|
||||
|
||||
```json
|
||||
{
|
||||
"agentName": "multi-tool-agent",
|
||||
"userId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"podRunningTimeSeconds": 5,
|
||||
"toolsUsed": ["search-api", "weather-api"],
|
||||
"startTime": "2026-01-29T11:45:30.000000+00:00",
|
||||
"endTime": "2026-01-29T11:45:35.000000+00:00",
|
||||
"requestId": "api-1738151130"
|
||||
}
|
||||
```
|
||||
|
||||
### 回调触发时机
|
||||
|
||||
- 每次 `/tools/call` 调用
|
||||
- 每次 `/tools/batch-call` 调用
|
||||
- 每次 MCP `tools/call` 请求
|
||||
|
||||
### 传递 User ID
|
||||
|
||||
通过以下方式传递 `user_id` 用于计费:
|
||||
|
||||
1. **请求参数**:在请求体中添加 `user_id` 字段
|
||||
2. **请求头**:设置 `X-User-ID` 头
|
||||
3. **环境变量**:Agent 启动时设置 `USER_ID` 环境变量
|
||||
|
||||
---
|
||||
|
||||
## ❌ 错误码定义
|
||||
|
||||
| HTTP 状态码 | 错误代码 | 说明 |
|
||||
|------------|----------|------|
|
||||
| 400 | invalid_config | 配置格式无效 |
|
||||
| 400 | invalid_url | URL 格式无效 |
|
||||
| 400 | invalid_method | HTTP 方法无效 |
|
||||
| 400 | invalid_auth | 认证配置无效 |
|
||||
| 400 | invalid_schema | JSON Schema 格式无效 |
|
||||
| 404 | tool_not_found | 工具不存在 |
|
||||
| 404 | agent_not_found | Agent 不存在 |
|
||||
| 409 | tool_name_exists | 工具名称已存在(同一用户下) |
|
||||
| 409 | tool_in_use | 工具正在被 Agent 使用 |
|
||||
| 500 | generation_failed | 工具代码生成失败 |
|
||||
| 500 | deployment_failed | Agent 部署失败 |
|
||||
| 500 | repo_creation_failed | Gitee 仓库创建失败 |
|
||||
|
||||
---
|
||||
|
||||
## 📋 完整使用流程示例
|
||||
|
||||
### 1. 创建多个工具
|
||||
|
||||
```bash
|
||||
# 工具1: 搜索 API
|
||||
TOOL1=$(curl -s -X POST http://20.212.121.126/external-tools/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "search-api",
|
||||
"description": "搜索API工具",
|
||||
"url": "https://api.search.com/v1/search",
|
||||
"method": "GET",
|
||||
"user_id": "user-123"
|
||||
}' | jq -r '.data.tool_ref_id')
|
||||
|
||||
# 工具2: 天气 API
|
||||
TOOL2=$(curl -s -X POST http://20.212.121.126/external-tools/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "weather-api",
|
||||
"description": "获取天气信息",
|
||||
"url": "https://api.weather.com/v1/current",
|
||||
"method": "GET",
|
||||
"user_id": "user-123"
|
||||
}' | jq -r '.data.tool_ref_id')
|
||||
|
||||
echo "工具1: $TOOL1"
|
||||
echo "工具2: $TOOL2"
|
||||
```
|
||||
|
||||
### 2. 创建包含多工具的 Agent
|
||||
|
||||
```bash
|
||||
AGENT_RESULT=$(curl -s -X POST http://20.212.121.126/external-tools/agents/create-with-tools \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"my-multi-tool-agent\",
|
||||
\"template\": \"custom_agent\",
|
||||
\"tool_refs\": [\"$TOOL1\", \"$TOOL2\"],
|
||||
\"config\": {\"user_id\": \"user-123\"}
|
||||
}")
|
||||
|
||||
AGENT_REF_ID=$(echo $AGENT_RESULT | jq -r '.agent_ref_id')
|
||||
echo "Agent Ref ID: $AGENT_REF_ID"
|
||||
```
|
||||
|
||||
### 3. 轮询构建状态
|
||||
|
||||
```bash
|
||||
while true; do
|
||||
STATUS=$(curl -s "http://20.212.121.126/external-tools/agents/${AGENT_REF_ID}/build-status")
|
||||
OVERALL=$(echo $STATUS | jq -r '.data.overall_status')
|
||||
echo "当前状态: $OVERALL"
|
||||
|
||||
if [ "$OVERALL" = "ready" ] || [ "$OVERALL" = "deployed" ]; then
|
||||
echo "✅ 构建完成!"
|
||||
break
|
||||
elif [ "$OVERALL" = "build_failed" ]; then
|
||||
echo "❌ 构建失败!"
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
```
|
||||
|
||||
### 4. 获取部署信息
|
||||
|
||||
```bash
|
||||
curl -s "http://20.212.121.126/external-tools/agents/${AGENT_REF_ID}/deployment-info" | jq .
|
||||
```
|
||||
|
||||
### 5. 访问 Agent
|
||||
|
||||
```bash
|
||||
DOMAIN=$(curl -s "http://20.212.121.126/external-tools/agents/${AGENT_REF_ID}/deployment-info" \
|
||||
| jq -r '.data.access_info.domain')
|
||||
|
||||
curl "http://${DOMAIN}/"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
如有疑问,请联系 Agent Manager 开发团队。
|
||||
|
||||
---
|
||||
|
||||
**文档更新记录**
|
||||
|
||||
| 日期 | 版本 | 更新内容 |
|
||||
|------|------|----------|
|
||||
| 2026-01-29 | v1.0 | 初始版本,实现 MCP-Server 外部工具接口规范 |
|
||||
| 2026-01-29 | v2.0 | 新增回调功能(计费)、多工具支持、构建状态查询、部署信息查询 |
|
||||
File diff suppressed because it is too large
Load Diff
+414
@@ -0,0 +1,414 @@
|
||||
# Agent Manager Kubernetes部署文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档说明如何在Azure Kubernetes Service (AKS)上部署Agent Manager服务。
|
||||
|
||||
## 前提条件
|
||||
|
||||
1. **Azure资源**
|
||||
- Azure订阅
|
||||
- AKS集群
|
||||
- Azure Container Registry (ACR)
|
||||
- Azure DNS Zone(用于自动配置域名)
|
||||
|
||||
2. **本地工具**
|
||||
- `kubectl` (Kubernetes命令行工具)
|
||||
- `az` (Azure CLI)
|
||||
- Docker (用于构建镜像)
|
||||
|
||||
3. **权限要求**
|
||||
- AKS集群的管理员权限
|
||||
- ACR的推送权限
|
||||
- DNS Zone的管理权限
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 1. 配置Azure凭据
|
||||
|
||||
#### 1.1 创建Service Principal(如果还没有)
|
||||
|
||||
```bash
|
||||
# 创建Service Principal
|
||||
az ad sp create-for-rbac \
|
||||
--name "agent-manager-sp" \
|
||||
--role contributor \
|
||||
--scopes /subscriptions/{subscription-id}
|
||||
|
||||
# 输出示例:
|
||||
# {
|
||||
# "appId": "c5ba26db-f180-425f-bac3-93708d853988",
|
||||
# "displayName": "agent-manager-sp",
|
||||
# "password": "ydt8Q~...",
|
||||
# "tenant": "263c3ff6-1be5-4141-8308-b188464fb297"
|
||||
# }
|
||||
```
|
||||
|
||||
#### 1.2 配置DNS权限(重要!⚠️)
|
||||
|
||||
**Agent Manager需要DNS Zone Contributor权限才能为创建的agent自动配置域名。**
|
||||
|
||||
使用提供的脚本配置DNS权限:
|
||||
|
||||
```bash
|
||||
# 方法1:使用自动化脚本(推荐)
|
||||
bash scripts/setup_dns_permissions.sh
|
||||
|
||||
# 方法2:手动配置
|
||||
AZURE_CLIENT_ID="your-service-principal-app-id"
|
||||
AZURE_SUBSCRIPTION_ID="your-subscription-id"
|
||||
AZURE_RESOURCE_GROUP="your-resource-group"
|
||||
AZURE_DNS_ZONE="your-dns-zone.com"
|
||||
|
||||
DNS_ZONE_ID="/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$AZURE_RESOURCE_GROUP/providers/Microsoft.Network/dnsZones/$AZURE_DNS_ZONE"
|
||||
|
||||
az role assignment create \
|
||||
--assignee $AZURE_CLIENT_ID \
|
||||
--role "DNS Zone Contributor" \
|
||||
--scope $DNS_ZONE_ID
|
||||
```
|
||||
|
||||
验证权限:
|
||||
```bash
|
||||
az role assignment list \
|
||||
--assignee $AZURE_CLIENT_ID \
|
||||
--scope $DNS_ZONE_ID \
|
||||
--output table
|
||||
```
|
||||
|
||||
#### 1.3 更新Kubernetes Secret
|
||||
|
||||
编辑 `k8s/agent-manager-secret.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: agent-manager-secret
|
||||
namespace: agent-manager
|
||||
type: Opaque
|
||||
stringData:
|
||||
AZURE_TENANT_ID: "263c3ff6-1be5-4141-8308-b188464fb297"
|
||||
AZURE_CLIENT_ID: "c5ba26db-f180-425f-bac3-93708d853988"
|
||||
AZURE_CLIENT_SECRET: "your-client-secret"
|
||||
AZURE_SUBSCRIPTION_ID: "45d7a360-af09-40fc-9afc-56dc475245ec"
|
||||
AZURE_RESOURCE_GROUP: "taiji-ai-v0"
|
||||
AZURE_DNS_ZONE: "taijiagnet.com"
|
||||
```
|
||||
|
||||
### 2. 配置ACR访问
|
||||
|
||||
创建ACR secret:
|
||||
|
||||
```bash
|
||||
# 获取ACR登录服务器
|
||||
ACR_NAME="your-acr-name"
|
||||
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
|
||||
|
||||
# 创建Docker registry secret
|
||||
kubectl create secret docker-registry acr-secret \
|
||||
--namespace agent-manager \
|
||||
--docker-server=$ACR_LOGIN_SERVER \
|
||||
--docker-username=$AZURE_CLIENT_ID \
|
||||
--docker-password=$AZURE_CLIENT_SECRET
|
||||
```
|
||||
|
||||
或使用脚本:
|
||||
```bash
|
||||
bash k8s/create-acr-secret.sh
|
||||
```
|
||||
|
||||
### 3. 构建和推送镜像
|
||||
|
||||
```bash
|
||||
# 构建镜像
|
||||
docker build -t $ACR_LOGIN_SERVER/agent-manager:latest .
|
||||
|
||||
# 登录ACR
|
||||
az acr login --name $ACR_NAME
|
||||
|
||||
# 推送镜像
|
||||
docker push $ACR_LOGIN_SERVER/agent-manager:latest
|
||||
```
|
||||
|
||||
### 4. 部署到Kubernetes
|
||||
|
||||
```bash
|
||||
# 创建命名空间
|
||||
kubectl apply -f k8s/agent-manager-namespace.yaml
|
||||
|
||||
# 创建RBAC(ServiceAccount、Role、RoleBinding)
|
||||
kubectl apply -f k8s/agent-manager-rbac.yaml
|
||||
|
||||
# 创建Secret(Azure凭据)
|
||||
kubectl apply -f k8s/agent-manager-secret.yaml
|
||||
|
||||
# 创建ACR Secret
|
||||
kubectl apply -f k8s/acr-secret.yaml
|
||||
|
||||
# 创建ConfigMap(可选)
|
||||
kubectl apply -f k8s/agent-manager-configmap.yaml
|
||||
|
||||
# 创建Deployment
|
||||
kubectl apply -f k8s/agent-manager-deployment.yaml
|
||||
|
||||
# 创建Service(LoadBalancer)
|
||||
kubectl apply -f k8s/agent-manager-service.yaml
|
||||
```
|
||||
|
||||
或使用一键部署脚本:
|
||||
```bash
|
||||
bash k8s/deploy.sh
|
||||
```
|
||||
|
||||
### 5. 验证部署
|
||||
|
||||
```bash
|
||||
# 检查Pod状态
|
||||
kubectl get pods -n agent-manager
|
||||
|
||||
# 检查Service和外网IP
|
||||
kubectl get svc -n agent-manager
|
||||
|
||||
# 查看日志
|
||||
kubectl logs -n agent-manager deployment/agent-manager
|
||||
|
||||
# 测试健康检查
|
||||
AGENT_MANAGER_IP=$(kubectl get svc agent-manager -n agent-manager -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||
curl http://$AGENT_MANAGER_IP/
|
||||
```
|
||||
|
||||
### 6. 测试Agent创建
|
||||
|
||||
```bash
|
||||
# 创建测试agent
|
||||
curl -X POST http://$AGENT_MANAGER_IP/agents \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "test-agent",
|
||||
"template": "echo_agent",
|
||||
"framework": "API",
|
||||
"config": {
|
||||
"user_id": "test-user"
|
||||
}
|
||||
}'
|
||||
|
||||
# 检查返回结果应包含:
|
||||
# - external_ip: 外网IP地址
|
||||
# - domain: 自动配置的域名 (test-agent.taijiagnet.com)
|
||||
# - domain_url: 域名访问地址
|
||||
# - recommended: 推荐访问地址(域名)
|
||||
|
||||
# 查看agent状态
|
||||
curl http://$AGENT_MANAGER_IP/agents/test-agent/status
|
||||
|
||||
# 测试域名访问
|
||||
curl http://test-agent.taijiagnet.com/
|
||||
|
||||
# 清理测试agent
|
||||
curl -X DELETE http://$AGENT_MANAGER_IP/agents/test-agent
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
k8s/
|
||||
├── README.md # 本文档
|
||||
├── agent-manager-namespace.yaml # Namespace定义
|
||||
├── agent-manager-rbac.yaml # RBAC配置(ServiceAccount、Role等)
|
||||
├── agent-manager-secret.yaml # Azure凭据Secret
|
||||
├── agent-manager-configmap.yaml # 配置文件ConfigMap
|
||||
├── agent-manager-deployment.yaml # Deployment定义
|
||||
├── agent-manager-service.yaml # LoadBalancer Service定义
|
||||
├── acr-secret.yaml # ACR访问Secret
|
||||
├── create-acr-secret.sh # 创建ACR Secret脚本
|
||||
└── deploy.sh # 一键部署脚本
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: Agent创建后没有返回域名信息
|
||||
|
||||
**症状:** 创建agent时返回外网IP但没有`domain`字段。
|
||||
|
||||
**原因:** Service Principal缺少DNS Zone的写权限。
|
||||
|
||||
**解决方案:**
|
||||
```bash
|
||||
# 运行DNS权限配置脚本
|
||||
bash scripts/setup_dns_permissions.sh
|
||||
|
||||
# 或手动分配权限(见上文"配置DNS权限"部分)
|
||||
```
|
||||
|
||||
详细信息见:`docs/DNS_ISSUE_FIX_REPORT.md`
|
||||
|
||||
### Q2: Pod启动失败,提示ImagePullBackOff
|
||||
|
||||
**原因:** 无法从ACR拉取镜像。
|
||||
|
||||
**解决方案:**
|
||||
1. 检查ACR secret是否正确创建
|
||||
2. 验证Service Principal有ACR的pull权限
|
||||
3. 确认镜像名称和标签正确
|
||||
|
||||
```bash
|
||||
# 检查ACR secret
|
||||
kubectl get secret acr-secret -n agent-manager
|
||||
|
||||
# 重新创建ACR secret
|
||||
bash k8s/create-acr-secret.sh
|
||||
```
|
||||
|
||||
### Q3: LoadBalancer IP一直处于Pending状态
|
||||
|
||||
**原因:** AKS集群配置或云提供商问题。
|
||||
|
||||
**解决方案:**
|
||||
1. 检查AKS集群是否支持LoadBalancer
|
||||
2. 查看Service事件:`kubectl describe svc agent-manager -n agent-manager`
|
||||
3. 确认Azure订阅有足够的配额
|
||||
|
||||
### Q4: 如何更新部署
|
||||
|
||||
```bash
|
||||
# 方法1:修改YAML文件后重新应用
|
||||
kubectl apply -f k8s/agent-manager-deployment.yaml
|
||||
|
||||
# 方法2:更新镜像
|
||||
kubectl set image deployment/agent-manager \
|
||||
agent-manager=your-acr.azurecr.io/agent-manager:new-tag \
|
||||
-n agent-manager
|
||||
|
||||
# 方法3:编辑Deployment
|
||||
kubectl edit deployment agent-manager -n agent-manager
|
||||
|
||||
# 查看滚动更新状态
|
||||
kubectl rollout status deployment/agent-manager -n agent-manager
|
||||
```
|
||||
|
||||
### Q5: 如何查看日志
|
||||
|
||||
```bash
|
||||
# 查看所有Pod日志
|
||||
kubectl logs -n agent-manager -l app=agent-manager
|
||||
|
||||
# 查看特定Pod日志
|
||||
kubectl logs -n agent-manager <pod-name>
|
||||
|
||||
# 实时跟踪日志
|
||||
kubectl logs -n agent-manager -l app=agent-manager -f
|
||||
|
||||
# 查看前一个容器的日志(如果Pod重启过)
|
||||
kubectl logs -n agent-manager <pod-name> --previous
|
||||
```
|
||||
|
||||
## 监控和维护
|
||||
|
||||
### 资源使用
|
||||
|
||||
```bash
|
||||
# 查看Pod资源使用
|
||||
kubectl top pods -n agent-manager
|
||||
|
||||
# 查看Node资源使用
|
||||
kubectl top nodes
|
||||
```
|
||||
|
||||
### 扩缩容
|
||||
|
||||
```bash
|
||||
# 手动扩容
|
||||
kubectl scale deployment agent-manager \
|
||||
--replicas=3 \
|
||||
-n agent-manager
|
||||
|
||||
# 自动扩缩容(HPA)
|
||||
kubectl autoscale deployment agent-manager \
|
||||
--cpu-percent=80 \
|
||||
--min=2 \
|
||||
--max=10 \
|
||||
-n agent-manager
|
||||
```
|
||||
|
||||
### 健康检查
|
||||
|
||||
Agent Manager提供以下健康检查端点:
|
||||
|
||||
- `GET /` - 基本健康检查
|
||||
- `GET /templates` - 模板列表(验证数据库连接)
|
||||
- `GET /agents` - Agent列表(验证K8s连接)
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
1. **Secret管理**
|
||||
- 不要将Secret提交到版本控制
|
||||
- 使用Azure Key Vault或Kubernetes Secrets加密
|
||||
- 定期轮换凭据
|
||||
|
||||
2. **RBAC**
|
||||
- 使用最小权限原则
|
||||
- 为不同环境使用不同的Service Principal
|
||||
- 定期审计权限分配
|
||||
|
||||
3. **网络安全**
|
||||
- 考虑使用Private LoadBalancer
|
||||
- 配置Network Policy限制Pod间通信
|
||||
- 使用Ingress Controller配置TLS
|
||||
|
||||
4. **镜像安全**
|
||||
- 定期扫描镜像漏洞
|
||||
- 使用最新的基础镜像
|
||||
- 不要在镜像中包含敏感信息
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 诊断命令
|
||||
|
||||
```bash
|
||||
# 检查所有资源
|
||||
kubectl get all -n agent-manager
|
||||
|
||||
# 查看Pod详情
|
||||
kubectl describe pod <pod-name> -n agent-manager
|
||||
|
||||
# 查看事件
|
||||
kubectl get events -n agent-manager --sort-by='.lastTimestamp'
|
||||
|
||||
# 检查ServiceAccount
|
||||
kubectl get sa -n agent-manager
|
||||
kubectl describe sa agent-manager-sa -n agent-manager
|
||||
|
||||
# 检查RoleBinding
|
||||
kubectl get rolebinding -n agent-manager
|
||||
kubectl describe rolebinding agent-manager-role-binding -n agent-manager
|
||||
|
||||
# 进入Pod调试
|
||||
kubectl exec -it <pod-name> -n agent-manager -- /bin/bash
|
||||
```
|
||||
|
||||
### 日志级别
|
||||
|
||||
在Deployment中设置环境变量调整日志级别:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "DEBUG" # DEBUG, INFO, WARNING, ERROR
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Kubernetes官方文档](https://kubernetes.io/docs/)
|
||||
- [Azure Kubernetes Service文档](https://docs.microsoft.com/azure/aks/)
|
||||
- [Azure DNS文档](https://docs.microsoft.com/azure/dns/)
|
||||
- [Agent Manager API文档](../docs/API_DOCUMENTATION.md)
|
||||
- [DNS问题修复报告](../docs/DNS_ISSUE_FIX_REPORT.md)
|
||||
|
||||
## 联系支持
|
||||
|
||||
如有问题,请:
|
||||
1. 查看本文档的常见问题部分
|
||||
2. 查看`docs/DNS_ISSUE_FIX_REPORT.md`
|
||||
3. 查看agent-manager日志
|
||||
4. 联系开发团队
|
||||
@@ -31,7 +31,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: agent-manager
|
||||
image: agnettaiji.azurecr.io/agent-manager:latest-arm64
|
||||
image: agnettaiji.azurecr.io/ai-agents/agent-manager:latest
|
||||
imagePullPolicy: Always
|
||||
|
||||
ports:
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Non-destructive redeploy for agent-manager to a target cluster via kubeconfig/context.
|
||||
# - Applies: namespace, RBAC, deployment, service
|
||||
# - Does NOT apply configmap/secret by default (to avoid overwriting real credentials)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/redeploy-agent-manager-with-kubeconfig.sh /path/to/kubeconfig [context]
|
||||
#
|
||||
# Optional env:
|
||||
# APPLY_CONFIG=1 -> also apply k8s/agent-manager-configmap.yaml
|
||||
# APPLY_SECRET=1 -> also apply k8s/agent-manager-secret.yaml (DANGEROUS unless you edited it)
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
K8S_DIR="${ROOT_DIR}/k8s"
|
||||
|
||||
KUBECONFIG_FILE="${1:-}"
|
||||
CONTEXT="${2:-}"
|
||||
|
||||
if [[ -z "${KUBECONFIG_FILE}" ]]; then
|
||||
echo "ERROR: kubeconfig path required."
|
||||
echo "Usage: $0 /path/to/kubeconfig [context]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${KUBECONFIG_FILE}" ]]; then
|
||||
echo "ERROR: kubeconfig not found: ${KUBECONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export KUBECONFIG="${KUBECONFIG_FILE}"
|
||||
|
||||
echo "Using kubeconfig: ${KUBECONFIG}"
|
||||
if [[ -n "${CONTEXT}" ]]; then
|
||||
echo "Switching context: ${CONTEXT}"
|
||||
kubectl config use-context "${CONTEXT}" >/dev/null
|
||||
fi
|
||||
|
||||
echo "Target cluster:"
|
||||
kubectl cluster-info
|
||||
|
||||
echo "Applying namespace..."
|
||||
kubectl apply -f "${K8S_DIR}/agent-manager-namespace.yaml"
|
||||
|
||||
if [[ "${APPLY_CONFIG:-0}" == "1" ]]; then
|
||||
echo "Applying configmap (APPLY_CONFIG=1)..."
|
||||
kubectl apply -f "${K8S_DIR}/agent-manager-configmap.yaml"
|
||||
else
|
||||
echo "Skipping configmap (set APPLY_CONFIG=1 to apply)."
|
||||
fi
|
||||
|
||||
if [[ "${APPLY_SECRET:-0}" == "1" ]]; then
|
||||
echo "Applying secret (APPLY_SECRET=1)..."
|
||||
kubectl apply -f "${K8S_DIR}/agent-manager-secret.yaml"
|
||||
else
|
||||
echo "Skipping secret (set APPLY_SECRET=1 to apply)."
|
||||
fi
|
||||
|
||||
echo "Applying RBAC..."
|
||||
kubectl apply -f "${K8S_DIR}/agent-manager-rbac.yaml"
|
||||
|
||||
echo "Applying deployment + service..."
|
||||
kubectl apply -f "${K8S_DIR}/agent-manager-deployment.yaml"
|
||||
kubectl apply -f "${K8S_DIR}/agent-manager-service.yaml"
|
||||
|
||||
echo "Waiting for rollout..."
|
||||
kubectl rollout status deployment/agent-manager -n agent-manager --timeout=300s
|
||||
|
||||
echo ""
|
||||
echo "Pods:"
|
||||
kubectl get pods -n agent-manager -o wide
|
||||
echo ""
|
||||
echo "Service:"
|
||||
kubectl get svc agent-manager -n agent-manager -o wide
|
||||
|
||||
echo ""
|
||||
echo "Done."
|
||||
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Agent Manager DNS权限配置脚本
|
||||
# =============================================================================
|
||||
#
|
||||
# 用途:为Azure Service Principal分配DNS Zone管理权限
|
||||
#
|
||||
# 前提条件:
|
||||
# 1. 已安装Azure CLI (az命令)
|
||||
# 2. 已登录到正确的Azure账户 (az login)
|
||||
# 3. 已选择正确的subscription (az account set)
|
||||
# 4. 有足够权限分配角色(Owner或User Access Administrator)
|
||||
#
|
||||
# 使用方法:
|
||||
# bash scripts/setup_dns_permissions.sh
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
set -e # 遇到错误立即退出
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# 配置部分 - 根据实际环境修改
|
||||
# =============================================================================
|
||||
|
||||
# 从k8s secret读取配置(如果可用)
|
||||
if command -v kubectl &> /dev/null && kubectl get secret agent-manager-secret -n agent-manager &> /dev/null 2>&1; then
|
||||
log_info "从Kubernetes Secret读取配置..."
|
||||
|
||||
AZURE_CLIENT_ID=$(kubectl get secret agent-manager-secret -n agent-manager -o jsonpath='{.data.AZURE_CLIENT_ID}' 2>/dev/null | base64 -d)
|
||||
AZURE_SUBSCRIPTION_ID=$(kubectl get secret agent-manager-secret -n agent-manager -o jsonpath='{.data.AZURE_SUBSCRIPTION_ID}' 2>/dev/null | base64 -d)
|
||||
AZURE_RESOURCE_GROUP=$(kubectl get secret agent-manager-secret -n agent-manager -o jsonpath='{.data.AZURE_RESOURCE_GROUP}' 2>/dev/null | base64 -d)
|
||||
AZURE_DNS_ZONE=$(kubectl get secret agent-manager-secret -n agent-manager -o jsonpath='{.data.AZURE_DNS_ZONE}' 2>/dev/null | base64 -d)
|
||||
else
|
||||
log_warning "无法从Kubernetes读取配置,使用默认值"
|
||||
|
||||
# 默认配置
|
||||
AZURE_CLIENT_ID="${AZURE_CLIENT_ID:-c5ba26db-f180-425f-bac3-93708d853988}"
|
||||
AZURE_SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID:-45d7a360-af09-40fc-9afc-56dc475245ec}"
|
||||
AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-taiji-ai-v0}"
|
||||
AZURE_DNS_ZONE="${AZURE_DNS_ZONE:-taijiagnet.com}"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 主逻辑
|
||||
# =============================================================================
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Agent Manager DNS权限配置"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
log_info "配置信息:"
|
||||
echo " Service Principal ID: $AZURE_CLIENT_ID"
|
||||
echo " Subscription ID: $AZURE_SUBSCRIPTION_ID"
|
||||
echo " Resource Group: $AZURE_RESOURCE_GROUP"
|
||||
echo " DNS Zone: $AZURE_DNS_ZONE"
|
||||
echo ""
|
||||
|
||||
# 1. 检查Azure CLI
|
||||
log_info "检查Azure CLI..."
|
||||
if ! command -v az &> /dev/null; then
|
||||
log_error "Azure CLI未安装,请先安装: https://docs.microsoft.com/cli/azure/install-azure-cli"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Azure CLI已安装"
|
||||
|
||||
# 2. 检查Azure登录状态
|
||||
log_info "检查Azure登录状态..."
|
||||
if ! az account show &> /dev/null; then
|
||||
log_error "未登录Azure,请先运行: az login"
|
||||
exit 1
|
||||
fi
|
||||
log_success "已登录Azure"
|
||||
|
||||
# 3. 切换到正确的subscription
|
||||
log_info "切换到目标subscription..."
|
||||
CURRENT_SUB=$(az account show --query id -o tsv)
|
||||
if [ "$CURRENT_SUB" != "$AZURE_SUBSCRIPTION_ID" ]; then
|
||||
log_warning "当前subscription ($CURRENT_SUB) 与目标不同,正在切换..."
|
||||
az account set --subscription "$AZURE_SUBSCRIPTION_ID"
|
||||
log_success "已切换到subscription: $AZURE_SUBSCRIPTION_ID"
|
||||
else
|
||||
log_success "已在正确的subscription"
|
||||
fi
|
||||
|
||||
# 4. 验证DNS Zone存在
|
||||
log_info "验证DNS Zone是否存在..."
|
||||
if ! az network dns zone show \
|
||||
--name "$AZURE_DNS_ZONE" \
|
||||
--resource-group "$AZURE_RESOURCE_GROUP" \
|
||||
--output none 2>/dev/null; then
|
||||
log_error "DNS Zone '$AZURE_DNS_ZONE' 不存在于资源组 '$AZURE_RESOURCE_GROUP'"
|
||||
exit 1
|
||||
fi
|
||||
log_success "DNS Zone存在"
|
||||
|
||||
# 构建DNS Zone资源ID
|
||||
DNS_ZONE_ID="/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$AZURE_RESOURCE_GROUP/providers/Microsoft.Network/dnsZones/$AZURE_DNS_ZONE"
|
||||
|
||||
# 5. 检查当前权限
|
||||
log_info "检查Service Principal当前权限..."
|
||||
EXISTING_ROLES=$(az role assignment list \
|
||||
--assignee "$AZURE_CLIENT_ID" \
|
||||
--scope "$DNS_ZONE_ID" \
|
||||
--query "[].roleDefinitionName" \
|
||||
--output tsv)
|
||||
|
||||
if echo "$EXISTING_ROLES" | grep -q "DNS Zone Contributor"; then
|
||||
log_success "Service Principal已拥有DNS Zone Contributor角色"
|
||||
echo ""
|
||||
echo "当前角色分配:"
|
||||
az role assignment list \
|
||||
--assignee "$AZURE_CLIENT_ID" \
|
||||
--scope "$DNS_ZONE_ID" \
|
||||
--output table
|
||||
echo ""
|
||||
log_info "无需重复分配权限"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 6. 分配DNS Zone Contributor角色
|
||||
log_info "分配DNS Zone Contributor角色..."
|
||||
if az role assignment create \
|
||||
--assignee "$AZURE_CLIENT_ID" \
|
||||
--role "DNS Zone Contributor" \
|
||||
--scope "$DNS_ZONE_ID" \
|
||||
--output none; then
|
||||
log_success "DNS Zone Contributor角色分配成功"
|
||||
else
|
||||
log_error "角色分配失败,请检查是否有足够的权限"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 7. 验证权限分配
|
||||
log_info "验证权限分配..."
|
||||
sleep 3 # 等待权限生效
|
||||
|
||||
echo ""
|
||||
echo "当前角色分配:"
|
||||
az role assignment list \
|
||||
--assignee "$AZURE_CLIENT_ID" \
|
||||
--scope "$DNS_ZONE_ID" \
|
||||
--output table
|
||||
|
||||
echo ""
|
||||
log_success "✅ DNS权限配置完成!"
|
||||
echo ""
|
||||
log_info "Service Principal现在可以管理DNS Zone '$AZURE_DNS_ZONE' 的DNS记录"
|
||||
log_info "Agent Manager可以为创建的agent自动配置DNS域名"
|
||||
echo ""
|
||||
|
||||
# 8. 测试DNS权限(可选)
|
||||
read -p "是否要测试DNS记录创建权限?(y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
TEST_RECORD="test-dns-permission-check"
|
||||
TEST_IP="1.2.3.4"
|
||||
|
||||
log_info "创建测试DNS记录: $TEST_RECORD.$AZURE_DNS_ZONE -> $TEST_IP"
|
||||
|
||||
# 使用Azure REST API测试(模拟agent-manager的行为)
|
||||
log_warning "注意:此测试需要AZURE_CLIENT_SECRET,将跳过"
|
||||
log_info "请在agent-manager中创建一个测试agent来验证DNS功能"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_success "配置完成!"
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
"""
|
||||
工具存储管理器
|
||||
负责存储和管理外部数据工具的配置和代码
|
||||
符合 MCP-Server 调用规范
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ToolStorage:
|
||||
"""工具存储管理器 - 管理外部数据工具的配置和代码文件"""
|
||||
|
||||
def __init__(self, storage_path: str = None):
|
||||
"""
|
||||
初始化工具存储
|
||||
|
||||
Args:
|
||||
storage_path: 工具存储路径,默认使用 ./tool_storage
|
||||
"""
|
||||
self.storage_path = Path(storage_path or os.getenv("TOOL_STORAGE_PATH", "./tool_storage"))
|
||||
self.storage_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 内存缓存
|
||||
self._cache: Dict[str, Dict] = {}
|
||||
|
||||
# 加载已有工具
|
||||
self._load_existing_tools()
|
||||
|
||||
def _load_existing_tools(self):
|
||||
"""从文件系统加载已有的工具配置"""
|
||||
try:
|
||||
for tool_dir in self.storage_path.iterdir():
|
||||
if tool_dir.is_dir():
|
||||
config_file = tool_dir / "config.json"
|
||||
if config_file.exists():
|
||||
with open(config_file, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
self._cache[config["tool_ref_id"]] = config
|
||||
|
||||
logger.info(f"✅ 已加载 {len(self._cache)} 个工具配置")
|
||||
except Exception as e:
|
||||
logger.error(f"加载工具配置失败: {e}")
|
||||
|
||||
def _get_tool_dir(self, tool_ref_id: str) -> Path:
|
||||
"""获取工具的存储目录"""
|
||||
return self.storage_path / tool_ref_id
|
||||
|
||||
def save_tool(
|
||||
self,
|
||||
tool_ref_id: str,
|
||||
name: str,
|
||||
description: str,
|
||||
config: Dict,
|
||||
code: str,
|
||||
user_id: str,
|
||||
tenant_id: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
保存工具配置和代码
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
name: 工具名称
|
||||
description: 工具描述
|
||||
config: 工具配置(含认证、参数等)
|
||||
code: 生成的 Python 代码
|
||||
user_id: 用户 ID
|
||||
tenant_id: 租户 ID
|
||||
|
||||
Returns:
|
||||
保存结果
|
||||
"""
|
||||
tool_dir = self._get_tool_dir(tool_ref_id)
|
||||
tool_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 保存配置
|
||||
tool_data = {
|
||||
"tool_ref_id": tool_ref_id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"config": config,
|
||||
"user_id": user_id,
|
||||
"tenant_id": tenant_id,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"updated_at": datetime.utcnow().isoformat(),
|
||||
"status": "created"
|
||||
}
|
||||
|
||||
config_file = tool_dir / "config.json"
|
||||
with open(config_file, "w", encoding="utf-8") as f:
|
||||
json.dump(tool_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 保存代码文件
|
||||
code_file = tool_dir / f"{self._to_python_name(name)}.py"
|
||||
with open(code_file, "w", encoding="utf-8") as f:
|
||||
f.write(code)
|
||||
|
||||
# 更新缓存
|
||||
tool_data["code_file"] = str(code_file)
|
||||
self._cache[tool_ref_id] = tool_data
|
||||
|
||||
logger.info(f"✅ 工具已保存: {tool_ref_id}")
|
||||
return {"success": True, "tool_ref_id": tool_ref_id, "code_file": str(code_file)}
|
||||
|
||||
def get_tool(self, tool_ref_id: str) -> Optional[Dict]:
|
||||
"""
|
||||
获取工具配置
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
|
||||
Returns:
|
||||
工具配置,不存在返回 None
|
||||
"""
|
||||
# 先从缓存获取
|
||||
if tool_ref_id in self._cache:
|
||||
return self._cache[tool_ref_id]
|
||||
|
||||
# 从文件系统加载
|
||||
tool_dir = self._get_tool_dir(tool_ref_id)
|
||||
config_file = tool_dir / "config.json"
|
||||
|
||||
if config_file.exists():
|
||||
with open(config_file, "r", encoding="utf-8") as f:
|
||||
tool_data = json.load(f)
|
||||
self._cache[tool_ref_id] = tool_data
|
||||
return tool_data
|
||||
|
||||
return None
|
||||
|
||||
def get_tool_code(self, tool_ref_id: str) -> Optional[str]:
|
||||
"""
|
||||
获取工具代码
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
|
||||
Returns:
|
||||
工具代码,不存在返回 None
|
||||
"""
|
||||
tool = self.get_tool(tool_ref_id)
|
||||
if not tool:
|
||||
return None
|
||||
|
||||
name = tool.get("name", "tool")
|
||||
tool_dir = self._get_tool_dir(tool_ref_id)
|
||||
code_file = tool_dir / f"{self._to_python_name(name)}.py"
|
||||
|
||||
if code_file.exists():
|
||||
with open(code_file, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
return None
|
||||
|
||||
def update_tool(self, tool_ref_id: str, updates: Dict) -> Optional[Dict]:
|
||||
"""
|
||||
更新工具配置
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
updates: 更新内容
|
||||
|
||||
Returns:
|
||||
更新后的配置
|
||||
"""
|
||||
tool = self.get_tool(tool_ref_id)
|
||||
if not tool:
|
||||
return None
|
||||
|
||||
# 更新配置
|
||||
if "description" in updates:
|
||||
tool["description"] = updates["description"]
|
||||
tool["config"]["description"] = updates["description"]
|
||||
|
||||
for key in ["url", "method", "headers", "auth", "request_params", "request_body", "timeout"]:
|
||||
if key in updates and updates[key] is not None:
|
||||
tool["config"][key] = updates[key]
|
||||
|
||||
tool["updated_at"] = datetime.utcnow().isoformat()
|
||||
|
||||
# 保存到文件
|
||||
tool_dir = self._get_tool_dir(tool_ref_id)
|
||||
config_file = tool_dir / "config.json"
|
||||
|
||||
save_data = {k: v for k, v in tool.items() if k != "code_file"}
|
||||
with open(config_file, "w", encoding="utf-8") as f:
|
||||
json.dump(save_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 更新缓存
|
||||
self._cache[tool_ref_id] = tool
|
||||
|
||||
return tool
|
||||
|
||||
def delete_tool(self, tool_ref_id: str) -> bool:
|
||||
"""
|
||||
删除工具
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
tool_dir = self._get_tool_dir(tool_ref_id)
|
||||
|
||||
if tool_dir.exists():
|
||||
import shutil
|
||||
shutil.rmtree(tool_dir)
|
||||
|
||||
if tool_ref_id in self._cache:
|
||||
del self._cache[tool_ref_id]
|
||||
|
||||
logger.info(f"✅ 工具已删除: {tool_ref_id}")
|
||||
return True
|
||||
|
||||
def list_tools(self, user_id: Optional[str] = None, tenant_id: Optional[str] = None) -> List[Dict]:
|
||||
"""
|
||||
列出工具
|
||||
|
||||
Args:
|
||||
user_id: 用户 ID 过滤
|
||||
tenant_id: 租户 ID 过滤
|
||||
|
||||
Returns:
|
||||
工具列表
|
||||
"""
|
||||
tools = list(self._cache.values())
|
||||
|
||||
if user_id:
|
||||
tools = [t for t in tools if t.get("user_id") == user_id]
|
||||
|
||||
if tenant_id:
|
||||
tools = [t for t in tools if t.get("tenant_id") == tenant_id]
|
||||
|
||||
return tools
|
||||
|
||||
def get_tools_by_refs(self, tool_refs: List[str]) -> List[Dict]:
|
||||
"""
|
||||
根据 tool_ref_id 列表获取工具
|
||||
|
||||
Args:
|
||||
tool_refs: tool_ref_id 列表
|
||||
|
||||
Returns:
|
||||
工具列表(仅返回存在的工具)
|
||||
"""
|
||||
tools = []
|
||||
for ref in tool_refs:
|
||||
tool = self.get_tool(ref)
|
||||
if tool:
|
||||
tools.append(tool)
|
||||
return tools
|
||||
|
||||
def check_tool_in_use(self, tool_ref_id: str) -> bool:
|
||||
"""
|
||||
检查工具是否被 Agent 使用
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
|
||||
Returns:
|
||||
是否被使用
|
||||
"""
|
||||
tool = self.get_tool(tool_ref_id)
|
||||
if not tool:
|
||||
return False
|
||||
|
||||
return tool.get("status") == "in_use" or bool(tool.get("used_by_agents"))
|
||||
|
||||
def mark_tool_in_use(self, tool_ref_id: str, agent_name: str) -> bool:
|
||||
"""
|
||||
标记工具被 Agent 使用
|
||||
|
||||
Args:
|
||||
tool_ref_id: 工具唯一标识
|
||||
agent_name: Agent 名称
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
tool = self.get_tool(tool_ref_id)
|
||||
if not tool:
|
||||
return False
|
||||
|
||||
if "used_by_agents" not in tool:
|
||||
tool["used_by_agents"] = []
|
||||
|
||||
if agent_name not in tool["used_by_agents"]:
|
||||
tool["used_by_agents"].append(agent_name)
|
||||
|
||||
tool["status"] = "in_use"
|
||||
|
||||
# 保存更新
|
||||
self.update_tool(tool_ref_id, {"status": "in_use"})
|
||||
return True
|
||||
|
||||
def _to_python_name(self, name: str) -> str:
|
||||
"""将名称转换为 Python 函数名格式"""
|
||||
import re
|
||||
name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
|
||||
if name and name[0].isdigit():
|
||||
name = '_' + name
|
||||
return name.lower()
|
||||
|
||||
|
||||
# 全局实例
|
||||
tool_storage = ToolStorage()
|
||||
Reference in New Issue
Block a user