From 377c7f4b0ed76453406cbd88cc0c4b74bd54695c Mon Sep 17 00:00:00 2001 From: zhanggangyong Date: Fri, 23 Jan 2026 07:30:24 +0000 Subject: [PATCH] =?UTF-8?q?Update:=20=E6=9B=B4=E6=96=B0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=92=8C=E6=B7=BB=E5=8A=A0=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CURSOR_MCP_SETUP.md | 145 ++++ agent_templates/agents/_template/Dockerfile | 20 + agent_templates/agents/_template/README.md | 67 ++ .../agents/_template/requirements.txt | 13 + .../agents/_template/run_api_server.py | 17 + .../agents/_template/src/__init__.py | 1 + .../agents/_template/src/server/__init__.py | 1 + .../agents/_template/src/server/api_server.py | 237 ++++++ .../agents/_template/src/server/mcp_server.py | 109 +++ .../agents/code_ai_agent/.cursorrules | 38 + .../agents/code_ai_agent/.dockerignore | 17 + .../agents/code_ai_agent/Dockerfile | 39 + .../agents/code_ai_agent/PROJECT_STRUCTURE.md | 167 ++++ .../agents/code_ai_agent/README.md | 105 +++ .../code_ai_agent/REFACTORING_SUMMARY.md | 157 ++++ .../code_ai_agent/cursor-mcp-config.json | 25 + .../agents/code_ai_agent/docker-compose.yml | 45 ++ .../code_ai_agent/docs/API_DOCUMENTATION.md | 638 +++++++++++++++ .../agents/code_ai_agent/docs/DEPLOYMENT.md | 391 ++++++++++ .../agents/code_ai_agent/docs/README_API.md | 121 +++ .../agents/code_ai_agent/requirements.txt | 10 + .../agents/code_ai_agent/run_api_server.py | 25 + .../code_ai_agent/run_mcp_http_server.py | 32 + .../agents/code_ai_agent/run_mcp_server.py | 51 ++ .../agents/code_ai_agent/src/__init__.py | 6 + .../code_ai_agent/src/client/__init__.py | 4 + .../code_ai_agent/src/client/mcp_client.py | 147 ++++ .../code_ai_agent/src/server/__init__.py | 30 + .../code_ai_agent/src/server/api_server.py | 724 ++++++++++++++++++ .../src/server/mcp_http_server.py | 402 ++++++++++ .../code_ai_agent/src/server/mcp_server.py | 654 ++++++++++++++++ .../agents/code_ai_agent/tests/__init__.py | 4 + .../agents/code_ai_agent/tests/test_agent.py | 266 +++++++ .../code_ai_agent/tests/test_mcp_server.py | 83 ++ .../agents/facebook_agent/Dockerfile | 37 + .../agents/facebook_agent/Dockerfile.mcp | 42 + .../agents/facebook_agent/__init__.py | 20 + .../agents/facebook_agent/agent/__init__.py | 9 + .../facebook_agent/agent/facebook_agent.py | 100 +++ agent_templates/agents/facebook_agent/api.py | 548 +++++++++++++ .../agents/facebook_agent/clients/__init__.py | 10 + .../facebook_agent/clients/facebook_client.py | 141 ++++ .../facebook_agent/clients/litellm_client.py | 176 +++++ .../agents/facebook_agent/config.py | 89 +++ .../facebook_agent/cursor-mcp-config.json | 28 + .../agents/facebook_agent/docker-compose.yml | 31 + .../agents/facebook_agent/docker-test.sh | 94 +++ .../agents/facebook_agent/k8s_deployment.yaml | 131 ++++ agent_templates/agents/facebook_agent/main.py | 121 +++ .../agents/facebook_agent/mcp_config.json | 22 + .../facebook_agent/mcp_config_remote.json | 15 + .../agents/facebook_agent/mcp_http_server.py | 327 ++++++++ .../agents/facebook_agent/mcp_server.py | 123 +++ .../agents/facebook_agent/models/__init__.py | 9 + .../agents/facebook_agent/models/schemas.py | 36 + .../agents/facebook_agent/requirements.txt | 28 + .../agents/facebook_agent/run_api.py | 29 + .../facebook_agent/run_mcp_http_server.py | 51 ++ .../agents/facebook_agent/run_mcp_server.py | 52 ++ .../agents/facebook_agent/start_api.sh | 38 + .../agents/facebook_agent/start_both.sh | 42 + .../agents/facebook_agent/start_mcp.sh | 44 ++ .../search_agent/agent/search_agent.py | 18 +- .../agents/search_agent/search_agent/main.py | 4 +- .../search_agent/modules/answer_generator.py | 8 +- .../search_agent/modules/content_extractor.py | 6 +- .../search_agent/modules/query_analyzer.py | 6 +- .../search_agent/modules/reflector.py | 6 +- .../search_agent/modules/result_processor.py | 8 +- .../search_agent/modules/search_executor.py | 6 +- .../search_agent/modules/search_planner.py | 4 +- .../search_agent/tools/jina_reader.py | 2 +- .../search_agent/tools/jina_reranker.py | 2 +- .../search_agent/search_agent/tools/serper.py | 2 +- .../search_agent_A2A.Dockerfile | 4 +- .../search_agent_MCP/mcp_server.py | 133 +++- .../search_agent_MCP.Dockerfile | 4 +- agent_templates/scripts/build_all_agents.sh | 6 + app.py | 251 ++++-- cursor-mcp-config-agents.json | 14 + k8s_manager.py | 103 ++- 81 files changed, 7634 insertions(+), 137 deletions(-) create mode 100644 CURSOR_MCP_SETUP.md create mode 100644 agent_templates/agents/_template/Dockerfile create mode 100644 agent_templates/agents/_template/README.md create mode 100644 agent_templates/agents/_template/requirements.txt create mode 100644 agent_templates/agents/_template/run_api_server.py create mode 100644 agent_templates/agents/_template/src/__init__.py create mode 100644 agent_templates/agents/_template/src/server/__init__.py create mode 100644 agent_templates/agents/_template/src/server/api_server.py create mode 100644 agent_templates/agents/_template/src/server/mcp_server.py create mode 100644 agent_templates/agents/code_ai_agent/.cursorrules create mode 100644 agent_templates/agents/code_ai_agent/.dockerignore create mode 100644 agent_templates/agents/code_ai_agent/Dockerfile create mode 100644 agent_templates/agents/code_ai_agent/PROJECT_STRUCTURE.md create mode 100644 agent_templates/agents/code_ai_agent/README.md create mode 100644 agent_templates/agents/code_ai_agent/REFACTORING_SUMMARY.md create mode 100644 agent_templates/agents/code_ai_agent/cursor-mcp-config.json create mode 100644 agent_templates/agents/code_ai_agent/docker-compose.yml create mode 100644 agent_templates/agents/code_ai_agent/docs/API_DOCUMENTATION.md create mode 100644 agent_templates/agents/code_ai_agent/docs/DEPLOYMENT.md create mode 100644 agent_templates/agents/code_ai_agent/docs/README_API.md create mode 100644 agent_templates/agents/code_ai_agent/requirements.txt create mode 100644 agent_templates/agents/code_ai_agent/run_api_server.py create mode 100644 agent_templates/agents/code_ai_agent/run_mcp_http_server.py create mode 100644 agent_templates/agents/code_ai_agent/run_mcp_server.py create mode 100644 agent_templates/agents/code_ai_agent/src/__init__.py create mode 100644 agent_templates/agents/code_ai_agent/src/client/__init__.py create mode 100644 agent_templates/agents/code_ai_agent/src/client/mcp_client.py create mode 100644 agent_templates/agents/code_ai_agent/src/server/__init__.py create mode 100644 agent_templates/agents/code_ai_agent/src/server/api_server.py create mode 100644 agent_templates/agents/code_ai_agent/src/server/mcp_http_server.py create mode 100644 agent_templates/agents/code_ai_agent/src/server/mcp_server.py create mode 100644 agent_templates/agents/code_ai_agent/tests/__init__.py create mode 100644 agent_templates/agents/code_ai_agent/tests/test_agent.py create mode 100644 agent_templates/agents/code_ai_agent/tests/test_mcp_server.py create mode 100644 agent_templates/agents/facebook_agent/Dockerfile create mode 100644 agent_templates/agents/facebook_agent/Dockerfile.mcp create mode 100644 agent_templates/agents/facebook_agent/__init__.py create mode 100644 agent_templates/agents/facebook_agent/agent/__init__.py create mode 100644 agent_templates/agents/facebook_agent/agent/facebook_agent.py create mode 100644 agent_templates/agents/facebook_agent/api.py create mode 100644 agent_templates/agents/facebook_agent/clients/__init__.py create mode 100644 agent_templates/agents/facebook_agent/clients/facebook_client.py create mode 100644 agent_templates/agents/facebook_agent/clients/litellm_client.py create mode 100644 agent_templates/agents/facebook_agent/config.py create mode 100644 agent_templates/agents/facebook_agent/cursor-mcp-config.json create mode 100644 agent_templates/agents/facebook_agent/docker-compose.yml create mode 100755 agent_templates/agents/facebook_agent/docker-test.sh create mode 100644 agent_templates/agents/facebook_agent/k8s_deployment.yaml create mode 100644 agent_templates/agents/facebook_agent/main.py create mode 100644 agent_templates/agents/facebook_agent/mcp_config.json create mode 100644 agent_templates/agents/facebook_agent/mcp_config_remote.json create mode 100644 agent_templates/agents/facebook_agent/mcp_http_server.py create mode 100644 agent_templates/agents/facebook_agent/mcp_server.py create mode 100644 agent_templates/agents/facebook_agent/models/__init__.py create mode 100644 agent_templates/agents/facebook_agent/models/schemas.py create mode 100644 agent_templates/agents/facebook_agent/requirements.txt create mode 100644 agent_templates/agents/facebook_agent/run_api.py create mode 100644 agent_templates/agents/facebook_agent/run_mcp_http_server.py create mode 100644 agent_templates/agents/facebook_agent/run_mcp_server.py create mode 100755 agent_templates/agents/facebook_agent/start_api.sh create mode 100755 agent_templates/agents/facebook_agent/start_both.sh create mode 100755 agent_templates/agents/facebook_agent/start_mcp.sh create mode 100644 cursor-mcp-config-agents.json diff --git a/CURSOR_MCP_SETUP.md b/CURSOR_MCP_SETUP.md new file mode 100644 index 0000000..eea7fe4 --- /dev/null +++ b/CURSOR_MCP_SETUP.md @@ -0,0 +1,145 @@ +# Cursor MCP 连接配置指南 + +本文档说明如何配置 Cursor 以连接到 `code_ai_agent` 和 `facebook_agent` 的 MCP 服务器。 + +## 前置条件 + +1. 确保两个 agent 已成功部署并运行 +2. 获取 agent 的访问域名(例如:`test-code-ai-agent-4.taijiagnet.com`) + +## 配置步骤 + +### 1. 找到 Cursor MCP 配置文件 + +Cursor 的 MCP 配置文件通常位于: +- **macOS**: `~/Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` +- **Windows**: `%APPDATA%\Cursor\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json` +- **Linux**: `~/.config/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` + +### 2. 编辑配置文件 + +将以下内容添加到 Cursor 的 MCP 配置文件中: + +```json +{ + "mcpServers": { + "code-ai-agent": { + "url": "http://test-code-ai-agent-4.taijiagnet.com/mcp", + "type": "http", + "description": "代码助手 Agent - 支持代码生成、重构、审查和组织功能" + }, + "facebook-agent": { + "url": "http://test-facebook-agent-6.taijiagnet.com/mcp", + "type": "http", + "description": "Facebook 搜索 Agent - 支持 Facebook 内容搜索" + } + } +} +``` + +### 3. 重启 Cursor + +保存配置文件后,重启 Cursor 以使配置生效。 + +## 可用的工具 + +### code-ai-agent 工具列表 + +1. **generate_code** - 根据需求生成代码 +2. **refactor_code** - 重构代码,改进代码质量 +3. **review_code** - 审查代码,发现潜在问题 +4. **organize_code** - 智能分析代码并自动组织到合适的文件夹 +5. **classify_code** - 分析代码内容,确定其应该属于哪个类别 +6. **analyze_project** - 分析项目结构,提供项目概览和建议 +7. **suggest_folder_structure** - 根据项目描述,建议合理的文件夹结构 +8. **create_code_file** - 在指定文件夹中创建代码文件 + +### facebook-agent 工具列表 + +1. **search_facebook** - 搜索 Facebook 内容,返回相关帖子和 AI 生成的总结 + +## 测试连接 + +### 测试 code-ai-agent + +```bash +curl -X POST http://test-code-ai-agent-4.taijiagnet.com/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/list", + "id": 1 + }' +``` + +### 测试 facebook-agent + +```bash +curl -X POST http://test-facebook-agent-6.taijiagnet.com/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/list", + "id": 1 + }' +``` + +## 使用示例 + +### 在 Cursor 中使用 code-ai-agent + +配置完成后,你可以在 Cursor 的聊天界面中直接使用这些工具。例如: + +- "帮我生成一个 FastAPI 的 hello world 应用" +- "审查这段代码:`[粘贴代码]`" +- "重构这个函数以提高性能" +- "分析当前项目的结构" + +### 在 Cursor 中使用 facebook-agent + +- "搜索 Facebook 上关于 AI 的最新内容" +- "查找 Facebook 上关于旅游的帖子" + +## 故障排除 + +### 连接失败 + +1. 检查 agent 是否正在运行: + ```bash + kubectl get pods -n agent-test-code-ai-agent-4 + kubectl get pods -n agent-test-facebook-agent-6 + ``` + +2. 检查域名解析是否正常: + ```bash + curl -I http://test-code-ai-agent-4.taijiagnet.com/health + curl -I http://test-facebook-agent-6.taijiagnet.com/health + ``` + +3. 检查 MCP 端点是否可访问: + ```bash + curl -X POST http://test-code-ai-agent-4.taijiagnet.com/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' + ``` + +### 工具调用失败 + +1. 检查 agent 日志: + ```bash + kubectl logs -n agent-test-code-ai-agent-4 test-code-ai-agent-4 --tail=50 + kubectl logs -n agent-test-facebook-agent-6 test-facebook-agent-6 --tail=50 + ``` + +2. 确认环境变量配置正确(特别是 API keys 和模型名称) + +## 注意事项 + +1. **域名更新**:如果 agent 被重新部署,域名可能会改变,需要更新配置文件 +2. **网络访问**:确保 Cursor 可以访问 agent 的域名 +3. **HTTPS**:如果 agent 使用 HTTPS,需要将 URL 中的 `http://` 改为 `https://` + +## 参考 + +- [MCP 协议文档](https://modelcontextprotocol.io/) +- [Cursor MCP 配置文档](https://docs.cursor.com/) diff --git a/agent_templates/agents/_template/Dockerfile b/agent_templates/agents/_template/Dockerfile new file mode 100644 index 0000000..5252a14 --- /dev/null +++ b/agent_templates/agents/_template/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.12-slim + +WORKDIR /app + +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + +RUN apt-get update && apt-get install -y gcc curl && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +CMD ["python", "run_api_server.py"] diff --git a/agent_templates/agents/_template/README.md b/agent_templates/agents/_template/README.md new file mode 100644 index 0000000..904b4ba --- /dev/null +++ b/agent_templates/agents/_template/README.md @@ -0,0 +1,67 @@ +# Agent 模板 + +基于 **Pydantic AI** 的轻量级 Agent 模板。 + +## 快速开始 + +### 1. 复制模板 + +```bash +cp -r _template your_agent_name +cd your_agent_name +# 全局替换 "your_agent" 为你的 agent 名称 +``` + +### 2. 修改核心文件 + +- `src/server/mcp_server.py` - 添加你的 MCP 工具 +- `src/server/api_server.py` - 添加你的 API 端点(可选) + +### 3. 本地测试 + +```bash +python run_api_server.py +``` + +### 4. 构建镜像 + +```bash +docker build -t your-agent:latest . +``` + +### 5. 注册到 Agent Manager + +在 `k8s_manager.py` 中添加: + +```python +# TEMPLATE_PORTS +"your_agent": 8000, + +# image_map +"your_agent": "agnettaiji.azurecr.io/ai-agents/your-agent:latest", +``` + +在 `app.py` 的 `valid_templates` 中添加 `"your_agent"`。 + +## 项目结构 + +``` +your_agent/ +├── Dockerfile +├── requirements.txt +├── run_api_server.py # 启动脚本 +└── src/ + ├── __init__.py + └── server/ + ├── __init__.py + ├── api_server.py # FastAPI + MCP HTTP + └── mcp_server.py # MCP 工具定义 +``` + +## 环境变量 + +| 变量 | 必需 | 说明 | +|------|------|------| +| LITELLM_GATEWAY_URL | 是 | LiteLLM Gateway URL | +| LITELLM_MODEL | 否 | 模型名称,默认 taiji/gpt-4o-mini | +| API_PORT | 否 | 端口,默认 8000 | diff --git a/agent_templates/agents/_template/requirements.txt b/agent_templates/agents/_template/requirements.txt new file mode 100644 index 0000000..811555a --- /dev/null +++ b/agent_templates/agents/_template/requirements.txt @@ -0,0 +1,13 @@ +# Pydantic AI +pydantic-ai>=0.0.14 + +# MCP +mcp>=0.9.0 +fastmcp>=0.1.0 + +# FastAPI +fastapi>=0.109.0 +uvicorn[standard]>=0.27.0 + +# HTTP Client +aiohttp>=3.9.0 diff --git a/agent_templates/agents/_template/run_api_server.py b/agent_templates/agents/_template/run_api_server.py new file mode 100644 index 0000000..fdd7da9 --- /dev/null +++ b/agent_templates/agents/_template/run_api_server.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +"""启动 API 服务器""" +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) + +if __name__ == '__main__': + from src.server.api_server import app + import uvicorn + import os + + host = os.getenv('API_HOST', '0.0.0.0') + port = int(os.getenv('API_PORT', '8000')) + + print(f"🚀 启动 Agent API: http://{host}:{port}") + uvicorn.run(app, host=host, port=port, log_level="info") diff --git a/agent_templates/agents/_template/src/__init__.py b/agent_templates/agents/_template/src/__init__.py new file mode 100644 index 0000000..1982123 --- /dev/null +++ b/agent_templates/agents/_template/src/__init__.py @@ -0,0 +1 @@ +"""Agent 源代码包""" diff --git a/agent_templates/agents/_template/src/server/__init__.py b/agent_templates/agents/_template/src/server/__init__.py new file mode 100644 index 0000000..fc4a3a8 --- /dev/null +++ b/agent_templates/agents/_template/src/server/__init__.py @@ -0,0 +1 @@ +"""服务器模块""" diff --git a/agent_templates/agents/_template/src/server/api_server.py b/agent_templates/agents/_template/src/server/api_server.py new file mode 100644 index 0000000..32cf24b --- /dev/null +++ b/agent_templates/agents/_template/src/server/api_server.py @@ -0,0 +1,237 @@ +""" +HTTP API 服务器 + +提供 REST API 和 MCP HTTP/SSE 端点。 +""" +import json +import uuid +import os +from typing import Optional, Dict, Any, AsyncGenerator +from contextlib import asynccontextmanager + +from fastapi import FastAPI, HTTPException, Request, Header, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse, JSONResponse +from pydantic import BaseModel, Field + +from .mcp_server import TOOL_MAP, TOOL_LIST + +# ==================== 配置 ==================== + +SERVER_NAME = "Your Agent API" # 修改为你的 Agent 名称 + + +# ==================== FastAPI 应用 ==================== + +@asynccontextmanager +async def lifespan(app: FastAPI): + print(f"🚀 {SERVER_NAME} 启动") + yield + print(f"🛑 {SERVER_NAME} 关闭") + +app = FastAPI( + title=SERVER_NAME, + version="1.0.0", + lifespan=lifespan +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +# ==================== API Key 验证 ==================== + +async def verify_api_key( + api_key: Optional[str] = Header(None, alias="api-key"), + authorization: Optional[str] = Header(None) +) -> str: + """验证 API Key""" + if api_key and api_key.strip() and api_key.strip() != "sk": + return api_key.strip() + + if authorization: + key = authorization[7:].strip() if authorization.startswith("Bearer ") else authorization.strip() + if key and key != "sk": + return key + + raise HTTPException(status_code=401, detail="缺少 API Key") + + +def get_api_key_from_request(request: Request) -> Optional[str]: + """从请求头提取 API Key(不验证)""" + api_key = request.headers.get("api-key") or request.headers.get("api_key") + if not api_key: + auth = request.headers.get("Authorization") + if auth: + api_key = auth[7:] if auth.startswith("Bearer ") else auth + return api_key + + +# ==================== 健康检查 ==================== + +@app.get("/") +async def root(): + return { + "service": SERVER_NAME, + "status": "running", + "tools": list(TOOL_MAP.keys()) + } + + +@app.get("/health") +async def health(): + return {"status": "healthy", "service": SERVER_NAME} + + +# ==================== MCP 端点 ==================== + +sessions: Dict[str, Dict] = {} + + +async def handle_mcp_request(data: Dict, session_id: str = None, api_key: str = None) -> Dict: + """处理 MCP JSON-RPC 请求""" + method = data.get("method") + params = data.get("params", {}) + req_id = data.get("id") + + # tools/call 需要验证 API Key + if method == "tools/call" and (not api_key or api_key == "sk"): + return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32001, "message": "缺少 API Key"}} + + try: + if method == "initialize": + session_id = session_id or str(uuid.uuid4()) + sessions[session_id] = {"initialized": True} + return { + "jsonrpc": "2.0", "id": req_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": {"tools": {}}, + "serverInfo": {"name": SERVER_NAME, "version": "1.0.0"} + } + } + + elif method == "tools/list": + return {"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOL_LIST}} + + elif method == "tools/call": + tool_name = params.get("name") + args = params.get("arguments", {}) + + if tool_name not in TOOL_MAP: + raise ValueError(f"Unknown tool: {tool_name}") + + # 设置 API Key 到环境变量 + old_key = os.environ.get('OPENAI_API_KEY') + if api_key: + os.environ['OPENAI_API_KEY'] = api_key + + try: + result = await TOOL_MAP[tool_name](**args) + finally: + if old_key: + os.environ['OPENAI_API_KEY'] = old_key + + return { + "jsonrpc": "2.0", "id": req_id, + "result": {"content": [{"type": "text", "text": str(result)}]} + } + + elif method == "ping": + return {"jsonrpc": "2.0", "id": req_id, "result": {}} + + else: + raise ValueError(f"Unknown method: {method}") + + except Exception as e: + return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32603, "message": str(e)}} + + +@app.post("/mcp") +async def mcp_endpoint(request: Request): + """MCP HTTP 端点""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") + api_key = get_api_key_from_request(request) + response = await handle_mcp_request(body, session_id, api_key) + return JSONResponse(content=response, headers={"x-mcp-session-id": session_id or ""}) + except Exception as e: + return JSONResponse(status_code=400, content={"jsonrpc": "2.0", "error": {"code": -32700, "message": str(e)}}) + + +@app.get("/mcp/sse") +async def mcp_sse(request: Request): + """MCP SSE 端点""" + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def stream() -> AsyncGenerator[str, None]: + yield f"data: {json.dumps({'type': 'connection', 'sessionId': session_id})}\n\n" + import asyncio + while True: + await asyncio.sleep(30) + yield f"data: {json.dumps({'type': 'ping'})}\n\n" + + return StreamingResponse(stream(), media_type="text/event-stream", + headers={"Cache-Control": "no-cache", "x-mcp-session-id": session_id}) + + +@app.post("/mcp/sse") +async def mcp_sse_post(request: Request): + """MCP SSE POST 端点""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + api_key = get_api_key_from_request(request) + + async def stream() -> AsyncGenerator[str, None]: + response = await handle_mcp_request(body, session_id, api_key) + yield f"data: {json.dumps(response)}\n\n" + + return StreamingResponse(stream(), media_type="text/event-stream", + headers={"Cache-Control": "no-cache", "x-mcp-session-id": session_id}) + except Exception as e: + return JSONResponse(status_code=400, content={"jsonrpc": "2.0", "error": {"code": -32700, "message": str(e)}}) + + +# ==================== 业务 API(可选)==================== + +class QueryRequest(BaseModel): + """请求模型""" + query: str = Field(..., description="查询内容") + option: Optional[str] = Field(None, description="可选参数") + + +class QueryResponse(BaseModel): + """响应模型""" + success: bool + result: Optional[str] = None + error: Optional[str] = None + + +@app.post("/api/v1/query", response_model=QueryResponse) +async def api_query(request: QueryRequest, api_key: str = Depends(verify_api_key)): + """业务 API 端点(示例)""" + try: + # 设置 API Key + old_key = os.environ.get('OPENAI_API_KEY') + os.environ['OPENAI_API_KEY'] = api_key + + try: + result = await TOOL_MAP['your_tool'](query=request.query, option=request.option) + return QueryResponse(success=True, result=result) + finally: + if old_key: + os.environ['OPENAI_API_KEY'] = old_key + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +if __name__ == '__main__': + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/agent_templates/agents/_template/src/server/mcp_server.py b/agent_templates/agents/_template/src/server/mcp_server.py new file mode 100644 index 0000000..7cb8de6 --- /dev/null +++ b/agent_templates/agents/_template/src/server/mcp_server.py @@ -0,0 +1,109 @@ +""" +MCP 服务器 - 定义 Agent 工具 + +使用 Pydantic AI 和 FastMCP 框架。 +在此文件中添加你的 MCP 工具。 +""" +import json +import os +from typing import Optional + +from mcp.server.fastmcp import FastMCP +from pydantic_ai import Agent + +# ==================== 配置 ==================== + +# LiteLLM Gateway 配置 +_BASE_URL = os.getenv('OPENAI_BASE_URL', + os.getenv('LLM_BASE_URL', 'https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1')) +_API_KEY = os.getenv('OPENAI_API_KEY', 'sk') + +os.environ.setdefault('OPENAI_API_KEY', _API_KEY) +os.environ.setdefault('OPENAI_BASE_URL', _BASE_URL) + +# 模型名称(pydantic_ai 需要 openai: 前缀) +def _get_model_name() -> str: + model = os.getenv('MODEL_NAME', os.getenv('LITELLM_MODEL', 'taiji/gpt-4o-mini')) + return model if ':' in model else f'openai:{model}' + +MODEL_NAME = _get_model_name() + +# ==================== MCP 服务器 ==================== + +server = FastMCP('Your Agent') # 修改为你的 Agent 名称 + +# 系统提示词 +SYSTEM_PROMPT = '''你是一个专业的 AI 助手。 +请根据用户的需求提供帮助。''' + + +def get_agent() -> Agent: + """创建 Agent 实例(每次调用使用最新的 API Key)""" + return Agent(MODEL_NAME, system_prompt=SYSTEM_PROMPT) + + +# ==================== MCP 工具定义 ==================== + +@server.tool() +async def your_tool( + query: str, + option: Optional[str] = None +) -> str: + """ + 你的工具描述 + + Args: + query: 查询内容 + option: 可选参数 + + Returns: + 处理结果(JSON 格式) + """ + try: + # 1. 调用 AI Agent 处理 + result = await get_agent().run(f"请处理: {query}") + + # 2. 返回结果 + return json.dumps({ + "success": True, + "query": query, + "result": result.output + }, ensure_ascii=False, indent=2) + + except Exception as e: + return json.dumps({ + "success": False, + "error": str(e) + }, ensure_ascii=False) + + +# 添加更多工具... +# @server.tool() +# async def another_tool(...) -> str: +# pass + + +# ==================== 工具映射(供 API 使用)==================== + +TOOL_MAP = { + 'your_tool': your_tool, +} + +TOOL_LIST = [ + { + "name": "your_tool", + "description": "你的工具描述", + "inputSchema": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "查询内容"}, + "option": {"type": "string", "description": "可选参数"} + }, + "required": ["query"] + } + } +] + + +if __name__ == '__main__': + server.run() diff --git a/agent_templates/agents/code_ai_agent/.cursorrules b/agent_templates/agents/code_ai_agent/.cursorrules new file mode 100644 index 0000000..1d3863d --- /dev/null +++ b/agent_templates/agents/code_ai_agent/.cursorrules @@ -0,0 +1,38 @@ +# Cursor MCP 服务器配置 +# 将此配置添加到 Cursor 的 MCP 设置中 + +# 方式1: 本地 stdio 调用(推荐用于本地开发) +# { +# "mcpServers": { +# "code-assistant": { +# "command": "python", +# "args": ["/home/taiji/tools/Pydantic-ai/run_mcp_server.py", "--transport", "stdio"], +# "env": { +# "OPENAI_API_KEY": "your_api_key", +# "OPENAI_BASE_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1", +# "LITELLM_MODEL": "openai:taiji/gpt-4o-mini" +# } +# } +# } +# } + +# 方式2: 远程 HTTP 调用(部署到云虚拟机后使用) +# { +# "mcpServers": { +# "code-assistant": { +# "url": "http://your-vm-ip:8001/mcp", +# "type": "http" +# } +# } +# } + +# 方式3: 远程 SSE 调用(部署到云虚拟机后使用) +# { +# "mcpServers": { +# "code-assistant": { +# "url": "http://your-vm-ip:8001/mcp/sse", +# "type": "sse" +# } +# } +# } + diff --git a/agent_templates/agents/code_ai_agent/.dockerignore b/agent_templates/agents/code_ai_agent/.dockerignore new file mode 100644 index 0000000..1200947 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/.dockerignore @@ -0,0 +1,17 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +.venv +venv/ +env/ +*.egg-info +dist/ +build/ +.git +.gitignore +*.md +.DS_Store +test_*.py +*.log diff --git a/agent_templates/agents/code_ai_agent/Dockerfile b/agent_templates/agents/code_ai_agent/Dockerfile new file mode 100644 index 0000000..29a7cb9 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/Dockerfile @@ -0,0 +1,39 @@ +# Dockerfile for 代码助手 Agent 服务 +FROM python:3.12-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements.txt . + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY . . + +# 创建项目存储目录 +RUN mkdir -p /tmp/projects + +# 安装curl用于健康检查 +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +# 暴露端口 +EXPOSE 8000 8001 + +# 健康检查(默认检查8000端口,MCP服务会覆盖) +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# 启动命令(默认启动API服务器,可通过command覆盖) +CMD ["python", "run_api_server.py"] diff --git a/agent_templates/agents/code_ai_agent/PROJECT_STRUCTURE.md b/agent_templates/agents/code_ai_agent/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..b9c48ed --- /dev/null +++ b/agent_templates/agents/code_ai_agent/PROJECT_STRUCTURE.md @@ -0,0 +1,167 @@ +# 项目结构说明 + +## 📁 目录结构 + +``` +Pydantic-ai/ +├── src/ # 源代码目录 +│ ├── __init__.py # 包初始化文件 +│ ├── server/ # 服务器模块 +│ │ ├── __init__.py # 导出服务器相关功能 +│ │ ├── mcp_server.py # MCP 服务器(MCP 协议) +│ │ └── api_server.py # HTTP API 服务器(FastAPI) +│ └── client/ # 客户端模块 +│ ├── __init__.py +│ └── mcp_client.py # MCP 客户端示例 +│ +├── tests/ # 测试目录 +│ ├── __init__.py +│ ├── test_agent.py # Agent 功能测试 +│ └── test_mcp_server.py # MCP 服务器测试 +│ +├── docs/ # 文档目录 +│ ├── API_DOCUMENTATION.md # API 接口文档 +│ ├── DEPLOYMENT.md # 部署指南 +│ └── README_API.md # API 快速开始 +│ +├── run_api_server.py # HTTP API 服务器启动脚本 +├── run_mcp_server.py # MCP 服务器启动脚本 +│ +├── requirements.txt # Python 依赖 +├── Dockerfile # Docker 镜像配置 +├── docker-compose.yml # Docker Compose 配置 +├── .dockerignore # Docker 构建忽略文件 +├── README.md # 项目说明 +└── PROJECT_STRUCTURE.md # 本文件 +``` + +## 📝 文件说明 + +### 源代码 (`src/`) + +#### `src/server/` +- **`mcp_server.py`**: MCP 服务器实现,包含所有工具函数和 Agent 配置 +- **`api_server.py`**: HTTP API 服务器,将 MCP 工具包装为 REST API + +#### `src/client/` +- **`mcp_client.py`**: MCP 客户端示例,演示如何通过 MCP 协议调用服务 + +### 测试 (`tests/`) + +- **`test_agent.py`**: 直接测试 Agent 功能 +- **`test_mcp_server.py`**: 测试 MCP 服务器工具函数 + +### 文档 (`docs/`) + +- **`API_DOCUMENTATION.md`**: 完整的 API 接口文档 +- **`DEPLOYMENT.md`**: 部署指南和配置说明 +- **`README_API.md`**: API 快速开始指南 + +### 启动脚本 + +- **`run_api_server.py`**: 启动 HTTP API 服务器的入口脚本 +- **`run_mcp_server.py`**: 启动 MCP 服务器的入口脚本 + +## 🔧 导入路径 + +### 在项目内部导入 + +```python +# 从服务器模块导入工具函数 +from src.server.mcp_server import ( + generate_code, + refactor_code, + review_code, + # ... +) + +# 或者使用 __init__.py 中的导出 +from src.server import ( + generate_code, + refactor_code, + # ... +) +``` + +### 在测试中导入 + +```python +import sys +from pathlib import Path + +# 添加项目根目录到路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from src.server.mcp_server import code_assistant_agent +``` + +## 🚀 运行方式 + +### 本地运行 + +```bash +# 启动 HTTP API 服务器 +python run_api_server.py + +# 启动 MCP 服务器 +python run_mcp_server.py + +# 运行测试 +python -m pytest tests/ +# 或 +python tests/test_agent.py +``` + +### Docker 运行 + +```bash +# 构建镜像 +docker build -t code-assistant-agent . + +# 运行容器 +docker run -p 8000:8000 code-assistant-agent + +# 或使用 Docker Compose +docker-compose up -d +``` + +## 📦 模块组织原则 + +1. **按功能分类**: 服务器、客户端、测试分别放在不同目录 +2. **清晰的导入路径**: 使用 `src.server`、`src.client` 等清晰的模块路径 +3. **统一的入口**: 使用根目录的启动脚本,方便运行 +4. **文档集中**: 所有文档放在 `docs/` 目录 + +## 🔄 导入关系 + +``` +run_api_server.py + └─> src.server.api_server + └─> src.server.mcp_server (工具函数) + +run_mcp_server.py + └─> src.server.mcp_server + +src.client.mcp_client + └─> (通过命令行调用) src.server.mcp_server + +tests/test_*.py + └─> src.server.mcp_server +``` + +## ✅ 重构完成检查清单 + +- [x] 创建目录结构 +- [x] 移动文件到对应目录 +- [x] 更新所有导入语句 +- [x] 创建 `__init__.py` 文件 +- [x] 更新 Dockerfile 路径 +- [x] 创建启动脚本 +- [x] 更新测试文件导入 +- [x] 更新客户端文件路径引用 + +--- + +**最后更新**: 2025-01-08 + diff --git a/agent_templates/agents/code_ai_agent/README.md b/agent_templates/agents/code_ai_agent/README.md new file mode 100644 index 0000000..078c820 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/README.md @@ -0,0 +1,105 @@ +# Pydantic AI Agents + +使用 Pydantic AI 创建的两个示例 Agent:天气查询 Agent 和搜索 Agent。 + +## 安装依赖 + +```bash +# 激活虚拟环境 +source .venv/bin/activate + +# 安装依赖 +pip install -r requirements.txt +``` + +## 1. 天气查询 Agent + +### 配置 API Key(可选) + +如果要使用真实的天气 API(OpenWeatherMap),需要设置环境变量: + +```bash +export WEATHER_API_KEY=your_api_key_here +``` + +获取免费 API Key:https://openweathermap.org/api + +如果不设置 API Key,Agent 会返回模拟数据用于演示。 + +### 运行 + +```bash +python weather_agent.py +``` + +### 使用方式 + +Agent 支持以下类型的查询: +- "北京今天的天气怎么样?" +- "请查询上海的天气" +- "What's the weather in Tokyo?" + +Agent 会自动调用天气查询工具,获取并返回天气信息。 + +## 2. 搜索 Agent(A2A 模式) + +### 配置 + +搜索 Agent 使用以下服务: + +1. **LiteLLM 服务器**(已配置在代码中): + - 服务器地址:`https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1` + - API Key:已内置在代码中 + - 模型:`taiji/gpt-4o-mini` + + 也可以通过环境变量自定义: + ```bash + export OPENAI_BASE_URL=https://your-litellm-server.com/v1 + export OPENAI_API_KEY=your_litellm_api_key + export LITELLM_MODEL=your_model_name + ``` + +2. **Serper.dev 搜索 API**(已内置在代码中): + ```bash + export SERPER_API_KEY=your_api_key_here # 可选,代码中已有默认值 + ``` + +### 运行 + +```bash +python search_agent.py +``` + +### 功能特性 + +- **搜索 Agent**:直接执行网络搜索,返回结构化搜索结果 +- **协调 Agent(A2A 模式)**:接收用户查询,委托搜索 Agent 执行搜索,并整理结果返回给用户 + +### 使用方式 + +Agent 支持以下类型的查询: +- "苹果公司最新新闻" +- "Python 异步编程最佳实践" +- "What is machine learning?" +- "请帮我搜索人工智能的最新发展" + +### A2A(Agent-to-Agent)通信 + +搜索 Agent 演示了 A2A 通信模式: +1. **协调 Agent** 接收用户查询 +2. **协调 Agent** 调用搜索工具(内部使用搜索 Agent) +3. **搜索工具** 返回结果给协调 Agent +4. **协调 Agent** 整理并返回给用户 + +这种模式允许多个 Agent 协作完成复杂任务。 + +### 测试结果 + +运行 `python search_agent.py` 后,Agent 会: +1. 连接到 LiteLLM 服务器 +2. 使用 serper.dev API 执行网络搜索 +3. 整合搜索结果并返回给用户 +4. 展示 A2A 通信流程 + +测试输出显示 Agent 成功调用了搜索工具,并能够处理中英文查询。 + diff --git a/agent_templates/agents/code_ai_agent/REFACTORING_SUMMARY.md b/agent_templates/agents/code_ai_agent/REFACTORING_SUMMARY.md new file mode 100644 index 0000000..ecf12eb --- /dev/null +++ b/agent_templates/agents/code_ai_agent/REFACTORING_SUMMARY.md @@ -0,0 +1,157 @@ +# 代码重构总结 + +## ✅ 重构完成 + +项目已成功重构,按照功能将 Python 文件分类到不同文件夹,保持目录整洁。 + +## 📁 新的目录结构 + +``` +Pydantic-ai/ +├── src/ # 源代码 +│ ├── server/ # 服务器模块 +│ │ ├── mcp_server.py # MCP 服务器 +│ │ └── api_server.py # HTTP API 服务器 +│ └── client/ # 客户端模块 +│ └── mcp_client.py # MCP 客户端示例 +│ +├── tests/ # 测试 +│ ├── test_agent.py +│ └── test_mcp_server.py +│ +├── docs/ # 文档 +│ ├── API_DOCUMENTATION.md +│ ├── DEPLOYMENT.md +│ └── README_API.md +│ +├── run_api_server.py # HTTP API 启动脚本 +├── run_mcp_server.py # MCP 服务器启动脚本 +├── requirements.txt +├── Dockerfile +└── docker-compose.yml +``` + +## 🔄 导入路径更新 + +### 已更新的文件 + +1. **`src/server/api_server.py`** + - ✅ 更新:从 `import mcp_server` 改为 `from .mcp_server import ...` + - ✅ 使用相对导入,更符合 Python 包结构 + +2. **`src/client/mcp_client.py`** + - ✅ 更新:使用 `Path` 动态获取 `mcp_server.py` 路径 + - ✅ 支持从任何位置运行 + +3. **`tests/test_mcp_server.py`** + - ✅ 更新:从 `from mcp_server import` 改为 `from src.server.mcp_server import` + - ✅ 添加项目根目录到 Python 路径 + +4. **`Dockerfile`** + - ✅ 更新:启动命令改为 `python run_api_server.py` + - ✅ 使用统一的启动脚本 + +## 📝 新增文件 + +1. **`run_api_server.py`** - HTTP API 服务器启动脚本 +2. **`run_mcp_server.py`** - MCP 服务器启动脚本 +3. **`src/__init__.py`** - 源代码包初始化 +4. **`src/server/__init__.py`** - 服务器模块导出 +5. **`src/client/__init__.py`** - 客户端模块初始化 +6. **`tests/__init__.py`** - 测试模块初始化 +7. **`PROJECT_STRUCTURE.md`** - 项目结构说明文档 + +## 🚀 使用方式 + +### 启动 HTTP API 服务器 + +```bash +# 方式 1: 使用启动脚本(推荐) +python run_api_server.py + +# 方式 2: 直接运行模块 +python -m src.server.api_server +``` + +### 启动 MCP 服务器 + +```bash +# 方式 1: 使用启动脚本(推荐) +python run_mcp_server.py + +# 方式 2: 直接运行模块 +python -m src.server.mcp_server +``` + +### 运行测试 + +```bash +# 运行所有测试 +python tests/test_agent.py +python tests/test_mcp_server.py + +# 或使用 pytest +pytest tests/ +``` + +### Docker 部署 + +```bash +# 构建和运行(已更新路径) +docker-compose up -d +``` + +## ✅ 验证清单 + +- [x] 所有文件已移动到正确目录 +- [x] 所有导入语句已更新 +- [x] `__init__.py` 文件已创建 +- [x] 启动脚本已创建 +- [x] Dockerfile 路径已更新 +- [x] 测试文件导入已更新 +- [x] 客户端文件路径引用已更新 +- [x] 文档已移动到 docs/ 目录 + +## 📋 导入示例 + +### 在项目内部导入 + +```python +# 从服务器模块导入工具函数 +from src.server.mcp_server import generate_code, refactor_code + +# 或使用 __init__.py 中的导出 +from src.server import generate_code, refactor_code +``` + +### 在测试中导入 + +```python +import sys +from pathlib import Path + +# 添加项目根目录到路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from src.server.mcp_server import code_assistant_agent +``` + +## 🎯 重构优势 + +1. **清晰的目录结构**: 按功能分类,易于维护 +2. **标准的 Python 包结构**: 符合 Python 最佳实践 +3. **易于扩展**: 新增功能可以轻松添加到对应目录 +4. **导入路径清晰**: 使用 `src.server`、`src.client` 等明确路径 +5. **统一的启动方式**: 使用根目录启动脚本,方便运行 + +## 📚 相关文档 + +- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 详细的项目结构说明 +- [docs/API_DOCUMENTATION.md](./docs/API_DOCUMENTATION.md) - API 接口文档 +- [docs/DEPLOYMENT.md](./docs/DEPLOYMENT.md) - 部署指南 + +--- + +**重构完成时间**: 2025-01-08 + diff --git a/agent_templates/agents/code_ai_agent/cursor-mcp-config.json b/agent_templates/agents/code_ai_agent/cursor-mcp-config.json new file mode 100644 index 0000000..eaa527f --- /dev/null +++ b/agent_templates/agents/code_ai_agent/cursor-mcp-config.json @@ -0,0 +1,25 @@ +{ + "mcpServers": { + "code-assistant-stdio": { + "command": "python", + "args": ["/home/taiji/tools/Pydantic-ai/run_mcp_server.py", "--transport", "stdio"], + "env": { + "OPENAI_API_KEY": "sk-", + "OPENAI_BASE_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1", + "LITELLM_MODEL": "openai:taiji/gpt-4o-mini" + } + }, + "code-assistant-http": { + "url": "http://your-vm-ip:8001/mcp", + "type": "http", + "headers": { + "Authorization": "Bearer your-token-if-needed" + } + }, + "code-assistant-sse": { + "url": "http://your-vm-ip:8001/mcp/sse", + "type": "sse" + } + } +} + diff --git a/agent_templates/agents/code_ai_agent/docker-compose.yml b/agent_templates/agents/code_ai_agent/docker-compose.yml new file mode 100644 index 0000000..eb65a29 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/docker-compose.yml @@ -0,0 +1,45 @@ +version: '3.8' + +services: + code-assistant-api: + build: . + container_name: code-assistant-agent + ports: + - "8000:8000" + environment: + - OPENAI_API_KEY=${OPENAI_API_KEY:-sk-rxegkFOciNmQLhOHr3qP3A} + - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1} + - LITELLM_MODEL=${LITELLM_MODEL:-openai:taiji/gpt-4o-mini} + - API_HOST=0.0.0.0 + - API_PORT=8000 + volumes: + - ./projects:/tmp/projects + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + code-assistant-mcp: + build: . + container_name: code-assistant-mcp + ports: + - "8001:8001" + environment: + - OPENAI_API_KEY=${OPENAI_API_KEY:-sk-rxegkFOciNmQLhOHr3qP3A} + - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1} + - LITELLM_MODEL=${LITELLM_MODEL:-openai:taiji/gpt-4o-mini} + - MCP_HOST=0.0.0.0 + - MCP_PORT=8001 + volumes: + - ./projects:/tmp/projects + restart: unless-stopped + command: python run_mcp_server.py --transport http --host 0.0.0.0 --port 8001 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8001/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/agent_templates/agents/code_ai_agent/docs/API_DOCUMENTATION.md b/agent_templates/agents/code_ai_agent/docs/API_DOCUMENTATION.md new file mode 100644 index 0000000..612239b --- /dev/null +++ b/agent_templates/agents/code_ai_agent/docs/API_DOCUMENTATION.md @@ -0,0 +1,638 @@ +# 代码助手 Agent API 接口文档 + +## 📋 目录 + +- [概述](#概述) +- [快速开始](#快速开始) +- [部署方式](#部署方式) +- [API 端点](#api-端点) + - [健康检查](#健康检查) + - [代码生成](#代码生成) + - [代码重构](#代码重构) + - [代码审查](#代码审查) + - [代码组织](#代码组织) + - [代码分类](#代码分类) + - [项目分析](#项目分析) + - [项目结构建议](#项目结构建议) + - [创建文件](#创建文件) +- [请求/响应格式](#请求响应格式) +- [错误处理](#错误处理) +- [使用示例](#使用示例) +- [MCP 协议调用](#mcp-协议调用) + +--- + +## 概述 + +代码助手 Agent 是一个基于 **MCP (Model Context Protocol)** 框架的代码助手服务,提供代码生成、重构、审查和组织功能。 + +### 服务架构 + +``` +用户请求 → HTTP API (FastAPI) → MCP 工具函数 → Pydantic AI Agent → LiteLLM Gateway +``` + +### 主要特性 + +- ✨ **代码生成**:根据自然语言需求生成高质量代码 +- 🔧 **代码重构**:改进代码质量、性能和可维护性 +- 🔍 **代码审查**:发现潜在问题、bug 和安全漏洞 +- 📁 **智能组织**:根据代码功能自动分类到合适文件夹 +- 📊 **项目分析**:分析项目结构并提供改进建议 + +### 技术栈 + +- **框架**: FastAPI + MCP (Model Context Protocol) +- **AI 引擎**: Pydantic AI +- **模型**: taiji/gpt-4o-mini (通过 litellm gateway) +- **协议**: HTTP REST API + MCP Protocol + +--- + +## 快速开始 + +### 1. 本地运行 + +```bash +# 安装依赖 +pip install -r requirements.txt + +# 启动 HTTP API 服务 +python api_server.py + +# 服务将在 http://localhost:8000 启动 +# API 文档: http://localhost:8000/docs +``` + +### 2. Docker 部署 + +```bash +# 构建镜像 +docker build -t code-assistant-agent . + +# 运行容器 +docker run -d \ + -p 8000:8000 \ + -e OPENAI_API_KEY=your_api_key \ + -e OPENAI_BASE_URL=your_gateway_url \ + code-assistant-agent +``` + +### 3. Docker Compose 部署 + +```bash +# 启动服务 +docker-compose up -d + +# 查看日志 +docker-compose logs -f +``` + +--- + +## 部署方式 + +### 环境变量配置 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `OPENAI_API_KEY` | LiteLLM Gateway API Key | `sk-rxegkFOciNmQLhOHr3qP3A` | +| `OPENAI_BASE_URL` | LiteLLM Gateway Base URL | `https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1` | +| `LITELLM_MODEL` | 模型名称 | `openai:taiji/gpt-4o-mini` | +| `API_HOST` | API 服务监听地址 | `0.0.0.0` | +| `API_PORT` | API 服务端口 | `8000` | + +### 云部署示例 + +#### Azure Container Apps + +```bash +az containerapp create \ + --name code-assistant-agent \ + --resource-group your-resource-group \ + --image your-registry/code-assistant-agent:latest \ + --target-port 8000 \ + --env-vars \ + OPENAI_API_KEY=your_key \ + OPENAI_BASE_URL=your_gateway_url +``` + +#### AWS ECS / EKS + +使用提供的 `Dockerfile` 构建镜像并部署到 ECS/EKS。 + +#### Google Cloud Run + +```bash +gcloud run deploy code-assistant-agent \ + --image gcr.io/your-project/code-assistant-agent \ + --platform managed \ + --set-env-vars OPENAI_API_KEY=your_key,OPENAI_BASE_URL=your_url +``` + +--- + +## API 端点 + +### 基础信息 + +- **Base URL**: `http://your-domain:8000` +- **API 版本**: `v1` +- **API 前缀**: `/api/v1` +- **文档地址**: `/docs` (Swagger UI) +- **ReDoc 文档**: `/redoc` + +### 统一响应格式 + +所有 API 响应使用统一格式: + +```json +{ + "success": true, + "data": { + "result": "响应内容" + }, + "message": "操作成功", + "error": null +} +``` + +错误响应: + +```json +{ + "success": false, + "data": null, + "message": null, + "error": "错误信息" +} +``` + +--- + +### 健康检查 + +#### GET `/health` + +检查服务健康状态。 + +**响应示例**: +```json +{ + "status": "healthy", + "service": "代码助手 Agent API" +} +``` + +--- + +### 代码生成 + +#### POST `/api/v1/generate-code` + +根据自然语言需求生成代码。 + +**请求体**: +```json +{ + "requirement": "创建一个用户认证服务类,包含登录、注册、密码验证功能", + "language": "python", + "style": "fastapi", + "project_root": "/tmp/projects" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `requirement` | string | ✅ | 代码需求描述 | +| `language` | string | ❌ | 编程语言,默认 `python` | +| `style` | string | ❌ | 代码风格,如 `fastapi`, `django` | +| `project_root` | string | ❌ | 项目根目录,默认 `/tmp/projects` | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "✅ 代码生成成功!\n\n📁 文件夹: /tmp/projects/services\n📄 文件名: auth_service.py\n..." + }, + "message": "代码生成成功" +} +``` + +--- + +### 代码重构 + +#### POST `/api/v1/refactor-code` + +重构代码,改进代码质量。 + +**请求体**: +```json +{ + "code_content": "def get_user(id):\n users = {1: {'name': 'Alice'}}\n return users[id]", + "refactoring_goal": "添加错误处理和类型提示" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `code_content` | string | ✅ | 要重构的代码 | +| `refactoring_goal` | string | ❌ | 重构目标 | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "✅ 代码重构完成!\n\n📝 重构后的代码:\n```python\n..." + }, + "message": "代码重构成功" +} +``` + +--- + +### 代码审查 + +#### POST `/api/v1/review-code` + +审查代码,发现问题和改进建议。 + +**请求体**: +```json +{ + "code_content": "def process_data(data):\n result = []\n for i in range(len(data)):\n if data[i] > 0:\n result.append(data[i] * 2)\n return result", + "file_path": "utils/processors.py" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `code_content` | string | ✅ | 要审查的代码 | +| `file_path` | string | ❌ | 文件路径(可选) | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "### 1. 潜在的 Bug\n- **索引访问**: 如果 data 为空...\n\n### 2. 代码质量问题\n..." + }, + "message": "代码审查完成" +} +``` + +--- + +### 代码组织 + +#### POST `/api/v1/organize-code` + +智能分析代码并自动组织到合适的文件夹。 + +**请求体**: +```json +{ + "code_content": "from datetime import datetime\n\ndef format_timestamp(ts):\n return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')", + "code_type": "utils", + "project_root": "/tmp/projects" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `code_content` | string | ✅ | 要组织的代码 | +| `code_type` | string | ❌ | 代码类型提示 | +| `project_root` | string | ❌ | 项目根目录 | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "✅ 代码已成功组织!\n\n📁 文件夹路径: /tmp/projects/utils\n..." + }, + "message": "代码组织成功" +} +``` + +--- + +### 代码分类 + +#### POST `/api/v1/classify-code` + +分析代码内容,确定其应该属于哪个类别。 + +**请求体**: +```json +{ + "code_content": "def calculate_total(items):\n total = sum(item['price'] * item['quantity'] for item in items)\n return total" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `code_content` | string | ✅ | 要分类的代码 | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "代码分析结果:\n\n1. **主要功能**: 计算商品总价..." + }, + "message": "代码分类完成" +} +``` + +--- + +### 项目分析 + +#### POST `/api/v1/analyze-project` + +分析项目结构,提供项目概览和改进建议。 + +**请求体**: +```json +{ + "project_root": "/tmp/projects", + "max_depth": 3 +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `project_root` | string | ❌ | 项目根目录,默认 `/tmp/projects` | +| `max_depth` | integer | ❌ | 最大扫描深度,默认 `3` | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "📊 项目分析报告\n\n📁 项目路径: /tmp/projects\n..." + }, + "message": "项目分析完成" +} +``` + +--- + +### 项目结构建议 + +#### POST `/api/v1/suggest-structure` + +根据项目描述,建议合理的文件夹结构。 + +**请求体**: +```json +{ + "project_description": "一个微服务架构的电商系统,包含用户服务、商品服务、订单服务和支付服务" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `project_description` | string | ✅ | 项目描述 | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "推荐的项目结构:\n\n```\necommerce-system/\n├── services/\n..." + }, + "message": "结构建议生成成功" +} +``` + +--- + +### 创建文件 + +#### POST `/api/v1/create-file` + +在指定文件夹中创建代码文件。 + +**请求体**: +```json +{ + "code_content": "from pydantic import BaseModel\n\nclass User(BaseModel):\n id: int\n name: str", + "folder_path": "models", + "file_name": "user.py", + "project_root": "/tmp/projects" +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `code_content` | string | ✅ | 代码内容 | +| `folder_path` | string | ✅ | 目标文件夹路径 | +| `file_name` | string | ✅ | 文件名 | +| `project_root` | string | ❌ | 项目根目录 | + +**响应示例**: +```json +{ + "success": true, + "data": { + "result": "✅ 文件创建成功!\n\n📁 文件夹: /tmp/projects/models\n..." + }, + "message": "文件创建成功" +} +``` + +--- + +## 请求/响应格式 + +### HTTP 状态码 + +- `200 OK`: 请求成功 +- `400 Bad Request`: 请求参数错误 +- `500 Internal Server Error`: 服务器内部错误 + +### Content-Type + +- 请求: `application/json` +- 响应: `application/json` + +--- + +## 错误处理 + +### 错误响应格式 + +```json +{ + "detail": "错误描述信息" +} +``` + +### 常见错误 + +1. **400 Bad Request**: 请求参数缺失或格式错误 +2. **500 Internal Server Error**: 服务器内部错误(AI 调用失败、文件操作失败等) + +--- + +## 使用示例 + +### Python 示例 + +```python +import requests + +BASE_URL = "http://localhost:8000" + +# 生成代码 +response = requests.post( + f"{BASE_URL}/api/v1/generate-code", + json={ + "requirement": "创建一个用户认证服务类", + "language": "python", + "style": "fastapi" + } +) +result = response.json() +print(result["data"]["result"]) + +# 审查代码 +response = requests.post( + f"{BASE_URL}/api/v1/review-code", + json={ + "code_content": "def func(): pass" + } +) +result = response.json() +print(result["data"]["result"]) +``` + +### cURL 示例 + +```bash +# 生成代码 +curl -X POST "http://localhost:8000/api/v1/generate-code" \ + -H "Content-Type: application/json" \ + -d '{ + "requirement": "创建一个用户认证服务类", + "language": "python", + "style": "fastapi" + }' + +# 审查代码 +curl -X POST "http://localhost:8000/api/v1/review-code" \ + -H "Content-Type: application/json" \ + -d '{ + "code_content": "def func(): pass" + }' +``` + +### JavaScript 示例 + +```javascript +const BASE_URL = 'http://localhost:8000'; + +// 生成代码 +async function generateCode() { + const response = await fetch(`${BASE_URL}/api/v1/generate-code`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + requirement: '创建一个用户认证服务类', + language: 'python', + style: 'fastapi' + }) + }); + + const result = await response.json(); + console.log(result.data.result); +} +``` + +--- + +## MCP 协议调用 + +除了 HTTP API,服务还支持通过 **MCP (Model Context Protocol)** 协议直接调用。 + +### MCP 客户端连接 + +```python +import asyncio +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + +async def main(): + server_params = StdioServerParameters( + command='python', + args=['mcp_server.py'], + env=os.environ + ) + + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + + # 调用工具 + result = await session.call_tool('generate_code', { + 'requirement': '创建一个用户服务类', + 'language': 'python' + }) + print(result.content[0].text) + +asyncio.run(main()) +``` + +### MCP 工具列表 + +| 工具名称 | 功能 | +|---------|------| +| `generate_code` | 生成代码 | +| `refactor_code` | 重构代码 | +| `review_code` | 审查代码 | +| `organize_code` | 组织代码 | +| `classify_code` | 分类代码 | +| `analyze_project` | 分析项目 | +| `suggest_folder_structure` | 建议项目结构 | +| `create_code_file` | 创建文件 | + +--- + +## 部署检查清单 + +- [ ] 配置环境变量(API Key、Gateway URL) +- [ ] 确保网络可以访问 LiteLLM Gateway +- [ ] 配置项目存储目录权限 +- [ ] 设置适当的资源限制(CPU、内存) +- [ ] 配置健康检查 +- [ ] 设置日志收集 +- [ ] 配置监控和告警 +- [ ] 设置 HTTPS(生产环境) + +--- + +## 技术支持 + +- **API 文档**: `/docs` (Swagger UI) +- **ReDoc 文档**: `/redoc` +- **健康检查**: `/health` + +--- + +**文档版本**: 1.0.0 +**最后更新**: 2025-01-08 diff --git a/agent_templates/agents/code_ai_agent/docs/DEPLOYMENT.md b/agent_templates/agents/code_ai_agent/docs/DEPLOYMENT.md new file mode 100644 index 0000000..72b6c0a --- /dev/null +++ b/agent_templates/agents/code_ai_agent/docs/DEPLOYMENT.md @@ -0,0 +1,391 @@ +# 代码助手 Agent 部署指南 + +## 📋 目录 + +- [部署方式](#部署方式) +- [环境配置](#环境配置) +- [Docker 部署](#docker-部署) +- [云平台部署](#云平台部署) +- [监控和日志](#监控和日志) +- [故障排查](#故障排查) + +--- + +## 部署方式 + +代码助手 Agent 支持多种部署方式: + +1. **本地部署** - 适合开发和测试 +2. **Docker 部署** - 适合单机部署 +3. **Docker Compose 部署** - 适合本地或小规模部署 +4. **云平台部署** - 适合生产环境 + +--- + +## 环境配置 + +### 必需环境变量 + +| 变量名 | 说明 | 示例 | +|--------|------|------| +| `OPENAI_API_KEY` | LiteLLM Gateway API Key | `sk-rxegkFOciNmQLhOHr3qP3A` | +| `OPENAI_BASE_URL` | LiteLLM Gateway Base URL | `https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1` | + +### 可选环境变量 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `LITELLM_MODEL` | 模型名称 | `openai:taiji/gpt-4o-mini` | +| `API_HOST` | API 服务监听地址 | `0.0.0.0` | +| `API_PORT` | API 服务端口 | `8000` | + +--- + +## Docker 部署 + +### 1. 构建镜像 + +```bash +docker build -t code-assistant-agent:latest . +``` + +### 2. 运行容器 + +```bash +docker run -d \ + --name code-assistant-agent \ + -p 8000:8000 \ + -e OPENAI_API_KEY=your_api_key \ + -e OPENAI_BASE_URL=your_gateway_url \ + -v $(pwd)/projects:/tmp/projects \ + code-assistant-agent:latest +``` + +### 3. 验证部署 + +```bash +# 检查容器状态 +docker ps | grep code-assistant-agent + +# 检查健康状态 +curl http://localhost:8000/health + +# 查看日志 +docker logs -f code-assistant-agent +``` + +--- + +## Docker Compose 部署 + +### 1. 配置环境变量 + +创建 `.env` 文件: + +```env +OPENAI_API_KEY=your_api_key +OPENAI_BASE_URL=your_gateway_url +LITELLM_MODEL=openai:taiji/gpt-4o-mini +API_HOST=0.0.0.0 +API_PORT=8000 +``` + +### 2. 启动服务 + +```bash +docker-compose up -d +``` + +### 3. 查看状态 + +```bash +# 查看服务状态 +docker-compose ps + +# 查看日志 +docker-compose logs -f + +# 停止服务 +docker-compose down +``` + +--- + +## 云平台部署 + +### Azure Container Apps + +#### 1. 准备镜像 + +```bash +# 登录 Azure Container Registry +az acr login --name your-registry + +# 构建并推送镜像 +docker build -t your-registry.azurecr.io/code-assistant-agent:latest . +docker push your-registry.azurecr.io/code-assistant-agent:latest +``` + +#### 2. 创建 Container App + +```bash +az containerapp create \ + --name code-assistant-agent \ + --resource-group your-resource-group \ + --image your-registry.azurecr.io/code-assistant-agent:latest \ + --target-port 8000 \ + --ingress external \ + --env-vars \ + OPENAI_API_KEY=your_key \ + OPENAI_BASE_URL=your_gateway_url \ + LITELLM_MODEL=openai:taiji/gpt-4o-mini +``` + +#### 3. 配置自动扩缩容 + +```bash +az containerapp update \ + --name code-assistant-agent \ + --resource-group your-resource-group \ + --min-replicas 1 \ + --max-replicas 10 \ + --cpu 1.0 \ + --memory 2.0Gi +``` + +### AWS ECS / EKS + +#### 1. 构建并推送镜像到 ECR + +```bash +# 登录 ECR +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin your-account.dkr.ecr.us-east-1.amazonaws.com + +# 构建并推送 +docker build -t code-assistant-agent:latest . +docker tag code-assistant-agent:latest your-account.dkr.ecr.us-east-1.amazonaws.com/code-assistant-agent:latest +docker push your-account.dkr.ecr.us-east-1.amazonaws.com/code-assistant-agent:latest +``` + +#### 2. 创建 ECS 任务定义 + +创建 `task-definition.json`: + +```json +{ + "family": "code-assistant-agent", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "1024", + "memory": "2048", + "containerDefinitions": [ + { + "name": "code-assistant-agent", + "image": "your-account.dkr.ecr.us-east-1.amazonaws.com/code-assistant-agent:latest", + "portMappings": [ + { + "containerPort": 8000, + "protocol": "tcp" + } + ], + "environment": [ + { + "name": "OPENAI_API_KEY", + "value": "your_api_key" + }, + { + "name": "OPENAI_BASE_URL", + "value": "your_gateway_url" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/code-assistant-agent", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "ecs" + } + } + } + ] +} +``` + +#### 3. 注册任务定义并创建服务 + +```bash +# 注册任务定义 +aws ecs register-task-definition --cli-input-json file://task-definition.json + +# 创建服务 +aws ecs create-service \ + --cluster your-cluster \ + --service-name code-assistant-agent \ + --task-definition code-assistant-agent \ + --desired-count 2 \ + --launch-type FARGATE \ + --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" +``` + +### Google Cloud Run + +#### 1. 构建并推送镜像 + +```bash +# 构建镜像 +gcloud builds submit --tag gcr.io/your-project/code-assistant-agent + +# 或者使用 Docker +docker build -t gcr.io/your-project/code-assistant-agent . +docker push gcr.io/your-project/code-assistant-agent +``` + +#### 2. 部署服务 + +```bash +gcloud run deploy code-assistant-agent \ + --image gcr.io/your-project/code-assistant-agent \ + --platform managed \ + --region us-central1 \ + --allow-unauthenticated \ + --set-env-vars \ + OPENAI_API_KEY=your_key,OPENAI_BASE_URL=your_url \ + --memory 2Gi \ + --cpu 1 \ + --min-instances 1 \ + --max-instances 10 +``` + +--- + +## 监控和日志 + +### 健康检查 + +服务提供健康检查端点: + +```bash +curl http://your-domain:8000/health +``` + +响应: +```json +{ + "status": "healthy", + "service": "代码助手 Agent API" +} +``` + +### 日志收集 + +#### Docker 日志 + +```bash +# 查看实时日志 +docker logs -f code-assistant-agent + +# 查看最近 100 行 +docker logs --tail 100 code-assistant-agent +``` + +#### 应用日志 + +服务使用标准输出,可以通过容器日志系统收集。 + +### 监控指标 + +建议监控以下指标: + +- **请求速率**: 每秒请求数 +- **响应时间**: API 响应时间 +- **错误率**: 5xx 错误比例 +- **资源使用**: CPU、内存使用率 +- **AI 调用**: LiteLLM Gateway 调用成功率 + +--- + +## 故障排查 + +### 常见问题 + +#### 1. 服务无法启动 + +**问题**: 容器启动失败 + +**排查步骤**: +```bash +# 查看容器日志 +docker logs code-assistant-agent + +# 检查环境变量 +docker exec code-assistant-agent env | grep OPENAI + +# 检查端口占用 +netstat -tulpn | grep 8000 +``` + +#### 2. API 调用失败 + +**问题**: 返回 500 错误 + +**排查步骤**: +- 检查 LiteLLM Gateway 连接 +- 验证 API Key 是否正确 +- 查看服务日志 + +#### 3. AI 调用超时 + +**问题**: 请求超时 + +**解决方案**: +- 增加超时时间配置 +- 检查网络连接 +- 验证 Gateway 服务状态 + +#### 4. 文件操作失败 + +**问题**: 无法创建文件 + +**排查步骤**: +```bash +# 检查目录权限 +ls -la /tmp/projects + +# 检查磁盘空间 +df -h + +# 检查容器挂载 +docker inspect code-assistant-agent | grep Mounts +``` + +### 调试模式 + +启用详细日志: + +```bash +docker run -e LOG_LEVEL=debug ... +``` + +--- + +## 安全建议 + +1. **API Key 管理**: 使用密钥管理服务(如 Azure Key Vault、AWS Secrets Manager) +2. **HTTPS**: 生产环境必须使用 HTTPS +3. **访问控制**: 配置 API 网关或反向代理进行访问控制 +4. **资源限制**: 设置适当的 CPU 和内存限制 +5. **网络隔离**: 使用私有网络和防火墙规则 + +--- + +## 性能优化 + +1. **连接池**: 配置 HTTP 客户端连接池 +2. **缓存**: 对频繁请求的结果进行缓存 +3. **异步处理**: 使用异步任务队列处理长时间任务 +4. **负载均衡**: 部署多个实例并使用负载均衡器 + +--- + +**文档版本**: 1.0.0 +**最后更新**: 2025-01-08 diff --git a/agent_templates/agents/code_ai_agent/docs/README_API.md b/agent_templates/agents/code_ai_agent/docs/README_API.md new file mode 100644 index 0000000..06df695 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/docs/README_API.md @@ -0,0 +1,121 @@ +# 代码助手 Agent - API 服务 + +## 🚀 快速开始 + +### 本地运行 + +```bash +# 1. 安装依赖 +pip install -r requirements.txt + +# 2. 启动服务 +python api_server.py + +# 3. 访问 API 文档 +# http://localhost:8000/docs +``` + +### Docker 部署 + +```bash +# 构建并运行 +docker-compose up -d + +# 查看日志 +docker-compose logs -f +``` + +## 📚 文档 + +- **[API 接口文档](./API_DOCUMENTATION.md)** - 完整的 API 接口说明 +- **[部署指南](./DEPLOYMENT.md)** - 云部署详细指南 + +## 🔧 配置 + +通过环境变量配置: + +```bash +export OPENAI_API_KEY=your_api_key +export OPENAI_BASE_URL=your_gateway_url +export API_PORT=8000 +``` + +## 📡 API 端点 + +所有 API 端点前缀: `/api/v1` + +- `POST /api/v1/generate-code` - 生成代码 +- `POST /api/v1/refactor-code` - 重构代码 +- `POST /api/v1/review-code` - 审查代码 +- `POST /api/v1/organize-code` - 组织代码 +- `POST /api/v1/classify-code` - 分类代码 +- `POST /api/v1/analyze-project` - 分析项目 +- `POST /api/v1/suggest-structure` - 建议项目结构 +- `POST /api/v1/create-file` - 创建文件 + +## 🔌 MCP 协议 + +服务基于 **MCP (Model Context Protocol)** 框架,也支持直接通过 MCP 协议调用。 + +查看 `mcp_server.py` 了解 MCP 工具定义。 + +## 📝 示例 + +### HTTP API 调用 + +```python +import requests + +response = requests.post( + "http://localhost:8000/api/v1/generate-code", + json={ + "requirement": "创建一个用户认证服务类", + "language": "python", + "style": "fastapi" + } +) +print(response.json()) +``` + +### MCP 协议调用 + +```python +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + +# 连接 MCP 服务器 +server_params = StdioServerParameters( + command='python', + args=['mcp_server.py'] +) + +async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + result = await session.call_tool('generate_code', { + 'requirement': '创建一个用户服务类' + }) + print(result.content[0].text) +``` + +## 🐳 部署 + +支持多种部署方式: + +- **Docker**: 使用提供的 Dockerfile +- **Docker Compose**: 使用 docker-compose.yml +- **云平台**: Azure Container Apps, AWS ECS, Google Cloud Run + +详细部署说明请查看 [DEPLOYMENT.md](./DEPLOYMENT.md) + +## 📊 健康检查 + +```bash +curl http://localhost:8000/health +``` + +## 🔍 监控 + +- API 文档: `/docs` (Swagger UI) +- ReDoc 文档: `/redoc` +- 健康检查: `/health` diff --git a/agent_templates/agents/code_ai_agent/requirements.txt b/agent_templates/agents/code_ai_agent/requirements.txt new file mode 100644 index 0000000..63ce91f --- /dev/null +++ b/agent_templates/agents/code_ai_agent/requirements.txt @@ -0,0 +1,10 @@ +pydantic-ai +httpx +mcp +fastmcp +fastapi +uvicorn[standard] +python-multipart +fastapi>=0.104.0 +uvicorn[standard]>=0.24.0 +python-multipart \ No newline at end of file diff --git a/agent_templates/agents/code_ai_agent/run_api_server.py b/agent_templates/agents/code_ai_agent/run_api_server.py new file mode 100644 index 0000000..d40f71d --- /dev/null +++ b/agent_templates/agents/code_ai_agent/run_api_server.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +""" +启动 HTTP API 服务器的入口脚本 +""" +import sys +from pathlib import Path + +# 添加项目根目录到 Python 路径 +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) + +if __name__ == '__main__': + from src.server.api_server import app + import uvicorn + import os + + host = os.getenv('API_HOST', '0.0.0.0') + port = int(os.getenv('API_PORT', '8000')) + + print(f"🚀 启动代码助手 Agent API") + print(f"📡 监听地址: http://{host}:{port}") + print(f"📚 API 文档: http://{host}:{port}/docs") + + uvicorn.run(app, host=host, port=port, log_level="info") + diff --git a/agent_templates/agents/code_ai_agent/run_mcp_http_server.py b/agent_templates/agents/code_ai_agent/run_mcp_http_server.py new file mode 100644 index 0000000..15ae3f8 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/run_mcp_http_server.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +""" +启动 MCP HTTP/SSE 服务器的入口脚本 +支持远程调用,供 Cursor 等客户端使用 +""" +import sys +import os +from pathlib import Path + +# 添加项目根目录到 Python 路径 +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) + +if __name__ == '__main__': + from src.server.mcp_http_server import app + import uvicorn + + host = os.getenv("MCP_HOST", "0.0.0.0") + port = int(os.getenv("MCP_PORT", "8001")) + + print(f"🚀 MCP HTTP/SSE Server 启动中...") + print(f"📡 HTTP 端点: http://{host}:{port}/mcp") + print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse") + print(f"📚 健康检查: http://{host}:{port}/health") + print(f"📋 工具列表: http://{host}:{port}/") + print() + print("💡 Cursor 配置示例:") + print(f' "url": "http://{host}:{port}/mcp"') + print(f' "type": "http"') + + uvicorn.run(app, host=host, port=port) + diff --git a/agent_templates/agents/code_ai_agent/run_mcp_server.py b/agent_templates/agents/code_ai_agent/run_mcp_server.py new file mode 100644 index 0000000..8cd92f3 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/run_mcp_server.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +""" +启动 MCP 服务器的入口脚本 +支持 stdio(本地)和 HTTP/SSE(远程)两种传输方式 +""" +import sys +import os +import argparse +from pathlib import Path + +# 添加项目根目录到 Python 路径 +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='启动 MCP 服务器') + parser.add_argument( + '--transport', + choices=['stdio', 'http', 'sse'], + default='stdio', + help='传输方式: stdio (本地), http (HTTP), sse (SSE)' + ) + parser.add_argument('--host', default='0.0.0.0', help='HTTP/SSE 服务器地址') + parser.add_argument('--port', type=int, default=8001, help='HTTP/SSE 服务器端口') + + args = parser.parse_args() + + if args.transport == 'stdio': + # stdio 模式(本地) + from src.server.mcp_server import server + print("🚀 MCP Server (stdio) 启动中...") + server.run() + else: + # HTTP/SSE 模式(远程) + from src.server.mcp_http_server import app + import uvicorn + + host = args.host + port = args.port + + print(f"🚀 MCP HTTP/SSE Server 启动中...") + print(f"📡 HTTP 端点: http://{host}:{port}/mcp") + print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse") + print(f"📚 健康检查: http://{host}:{port}/health") + print() + print("💡 Cursor 配置示例:") + print(f' "url": "http://{host}:{port}/mcp"') + print(f' "type": "{args.transport}"') + + uvicorn.run(app, host=host, port=port) + diff --git a/agent_templates/agents/code_ai_agent/src/__init__.py b/agent_templates/agents/code_ai_agent/src/__init__.py new file mode 100644 index 0000000..96e4644 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/__init__.py @@ -0,0 +1,6 @@ +""" +代码助手 Agent - 源代码包 +""" + +__version__ = "1.0.0" + diff --git a/agent_templates/agents/code_ai_agent/src/client/__init__.py b/agent_templates/agents/code_ai_agent/src/client/__init__.py new file mode 100644 index 0000000..5fd3532 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/client/__init__.py @@ -0,0 +1,4 @@ +""" +客户端模块 - MCP 客户端示例 +""" + diff --git a/agent_templates/agents/code_ai_agent/src/client/mcp_client.py b/agent_templates/agents/code_ai_agent/src/client/mcp_client.py new file mode 100644 index 0000000..f2e9e01 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/client/mcp_client.py @@ -0,0 +1,147 @@ +""" +MCP 客户端示例 - 真实的代码助手 Agent +演示如何使用代码助手 Agent 的各种功能 +""" +import asyncio +import os + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + + +async def main(): + """主函数:连接到 MCP 服务器并调用工具""" + # 配置服务器参数 + # 获取项目根目录 + import sys + from pathlib import Path + project_root = Path(__file__).parent.parent.parent + mcp_server_path = project_root / 'src' / 'server' / 'mcp_server.py' + + server_params = StdioServerParameters( + command='python', + args=[str(mcp_server_path)], + env=os.environ + ) + + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # 初始化会话 + await session.initialize() + + print("=" * 70) + print("🤖 代码助手 Agent - 真实功能演示") + print("=" * 70) + print() + + # 示例 1: 生成代码 + print("✨ 示例 1: 根据需求生成代码") + print("-" * 70) + result = await session.call_tool( + 'generate_code', + { + 'requirement': '创建一个用户认证服务类,包含登录、注册、密码验证功能', + 'language': 'python', + 'style': 'fastapi', + 'project_root': './demo_project' + } + ) + print(result.content[0].text) + print() + + # 示例 2: 代码审查 + print("🔍 示例 2: 代码审查") + print("-" * 70) + code_to_review = """ +def process_data(data): + result = [] + for i in range(len(data)): + if data[i] > 0: + result.append(data[i] * 2) + return result +""" + result = await session.call_tool( + 'review_code', + { + 'code_content': code_to_review, + 'file_path': 'utils/processors.py' + } + ) + print(result.content[0].text) + print() + + # 示例 3: 代码重构 + print("🔧 示例 3: 代码重构") + print("-" * 70) + code_to_refactor = """ +def get_user(id): + users = { + 1: {'name': 'Alice', 'age': 30}, + 2: {'name': 'Bob', 'age': 25} + } + return users.get(id) +""" + result = await session.call_tool( + 'refactor_code', + { + 'code_content': code_to_refactor, + 'refactoring_goal': '添加错误处理和类型提示' + } + ) + print(result.content[0].text) + print() + + # 示例 4: 智能组织代码 + print("📁 示例 4: 智能组织代码到合适文件夹") + print("-" * 70) + utility_code = """ +from datetime import datetime + +def format_timestamp(ts): + \"\"\"格式化时间戳为可读格式\"\"\" + return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') +""" + result = await session.call_tool( + 'organize_code', + { + 'code_content': utility_code, + 'project_root': './demo_project' + } + ) + print(result.content[0].text) + print() + + # 示例 5: 项目分析 + print("📊 示例 5: 分析项目结构") + print("-" * 70) + result = await session.call_tool( + 'analyze_project', + { + 'project_root': './demo_project', + 'max_depth': 2 + } + ) + print(result.content[0].text) + print() + + # 示例 6: 建议项目结构 + print("🏗️ 示例 6: 建议项目文件夹结构") + print("-" * 70) + result = await session.call_tool( + 'suggest_folder_structure', + { + 'project_description': '一个微服务架构的电商系统,包含用户服务、商品服务、订单服务和支付服务' + } + ) + print(result.content[0].text) + print() + + print("=" * 70) + print("✅ 所有功能演示完成!") + print("=" * 70) + print("\n💡 提示:生成的代码和文件已保存在 ./demo_project 目录中") + + +if __name__ == '__main__': + asyncio.run(main()) + diff --git a/agent_templates/agents/code_ai_agent/src/server/__init__.py b/agent_templates/agents/code_ai_agent/src/server/__init__.py new file mode 100644 index 0000000..1b5e2b4 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/server/__init__.py @@ -0,0 +1,30 @@ +""" +服务器模块 - MCP 服务器和 HTTP API 服务器 +""" + +from .mcp_server import ( + server, + code_assistant_agent, + organize_code, + suggest_folder_structure, + classify_code, + generate_code, + refactor_code, + review_code, + analyze_project, + create_code_file +) + +__all__ = [ + 'server', + 'code_assistant_agent', + 'organize_code', + 'suggest_folder_structure', + 'classify_code', + 'generate_code', + 'refactor_code', + 'review_code', + 'analyze_project', + 'create_code_file', +] + diff --git a/agent_templates/agents/code_ai_agent/src/server/api_server.py b/agent_templates/agents/code_ai_agent/src/server/api_server.py new file mode 100644 index 0000000..67b748a --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/server/api_server.py @@ -0,0 +1,724 @@ +""" +HTTP API 服务器 - 代码助手 Agent 服务 +将 MCP 服务包装为 HTTP API,支持云部署 +""" +import asyncio +import os +import json +import uuid +from contextlib import asynccontextmanager +from typing import Optional, Dict, Any, AsyncGenerator + +from fastapi import FastAPI, HTTPException, BackgroundTasks, Request, Header, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse, JSONResponse +from pydantic import BaseModel, Field +# 从同目录的 mcp_server 模块导入工具函数 +from .mcp_server import ( + organize_code, + suggest_folder_structure, + classify_code, + generate_code, + refactor_code, + review_code, + analyze_project, + create_code_file +) + +# 配置 +API_VERSION = "v1" +SERVER_NAME = "代码助手 Agent API" + +# 创建 FastAPI 应用 +@asynccontextmanager +async def lifespan(app: FastAPI): + """应用生命周期管理""" + # 启动时初始化 + print(f"🚀 {SERVER_NAME} 启动中...") + yield + # 关闭时清理 + print(f"🛑 {SERVER_NAME} 关闭中...") + +app = FastAPI( + title=SERVER_NAME, + description="基于 MCP 框架的代码助手 Agent 服务", + version="1.0.0", + lifespan=lifespan +) + +# 配置 CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 生产环境应限制具体域名 + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# ==================== 请求模型 ==================== + +class GenerateCodeRequest(BaseModel): + """代码生成请求""" + requirement: str = Field(..., description="代码需求描述") + language: str = Field(default="python", description="编程语言") + style: Optional[str] = Field(default=None, description="代码风格,如 'fastapi', 'django'") + project_root: str = Field(default="/tmp/projects", description="项目根目录路径") + + +class RefactorCodeRequest(BaseModel): + """代码重构请求""" + code_content: str = Field(..., description="要重构的代码内容") + refactoring_goal: Optional[str] = Field(default=None, description="重构目标") + + +class ReviewCodeRequest(BaseModel): + """代码审查请求""" + code_content: str = Field(..., description="要审查的代码内容") + file_path: Optional[str] = Field(default=None, description="文件路径(可选)") + + +class OrganizeCodeRequest(BaseModel): + """代码组织请求""" + code_content: str = Field(..., description="要组织的代码内容") + code_type: Optional[str] = Field(default=None, description="代码类型提示") + project_root: str = Field(default="/tmp/projects", description="项目根目录路径") + + +class ClassifyCodeRequest(BaseModel): + """代码分类请求""" + code_content: str = Field(..., description="要分类的代码内容") + + +class AnalyzeProjectRequest(BaseModel): + """项目分析请求""" + project_root: str = Field(default="/tmp/projects", description="项目根目录路径") + max_depth: int = Field(default=3, description="最大扫描深度") + + +class SuggestStructureRequest(BaseModel): + """项目结构建议请求""" + project_description: str = Field(..., description="项目描述") + + +class CreateFileRequest(BaseModel): + """创建文件请求""" + code_content: str = Field(..., description="代码内容") + folder_path: str = Field(..., description="目标文件夹路径") + file_name: str = Field(..., description="文件名") + project_root: str = Field(default="/tmp/projects", description="项目根目录路径") + + +# ==================== 响应模型 ==================== + +class APIResponse(BaseModel): + """统一 API 响应格式""" + success: bool = Field(..., description="是否成功") + data: Optional[Any] = Field(default=None, description="响应数据") + message: Optional[str] = Field(default=None, description="响应消息") + error: Optional[str] = Field(default=None, description="错误信息") + + +# ==================== API Key 验证 ==================== + +async def verify_api_key( + api_key: Optional[str] = Header(None, alias="api-key"), + authorization: Optional[str] = Header(None) +) -> str: + """ + 验证 API Key + + 支持从以下位置获取 API key: + 1. api-key 请求头 + 2. Authorization: Bearer 请求头 + 3. 环境变量 LLM_API_KEY 或 OPENAI_API_KEY(仅用于工具调用,不作为验证) + + 如果没有提供 API key,返回 401 错误 + """ + # 从 api-key 请求头获取 + if api_key: + if not api_key.strip() or api_key.strip() == "sk": + raise HTTPException( + status_code=401, + detail="无效的 API key。请提供有效的 API key。" + ) + return api_key.strip() + + # 从 Authorization 请求头获取 + if authorization: + if authorization.startswith("Bearer "): + api_key = authorization[7:].strip() + else: + api_key = authorization.strip() + + if not api_key or api_key == "sk": + raise HTTPException( + status_code=401, + detail="无效的 API key。请提供有效的 API key。" + ) + return api_key + + # 如果没有提供 API key,返回错误 + raise HTTPException( + status_code=401, + detail="缺少 API key。请在请求头中提供 'api-key' 或 'Authorization: Bearer '。" + ) + + +# ==================== 工具函数 ==================== + +async def call_mcp_tool(tool_name: str, params: Dict[str, Any], api_key: Optional[str] = None) -> str: + """ + 调用 MCP 工具 + + Args: + tool_name: 工具名称 + params: 工具参数 + api_key: API key(如果提供,会临时设置到环境变量中) + + Returns: + 工具返回结果 + """ + # 如果提供了 API key,临时设置到环境变量中 + old_api_key = None + if api_key: + old_api_key = os.environ.get('OPENAI_API_KEY') + os.environ['OPENAI_API_KEY'] = api_key + + try: + # 直接调用导入的工具函数 + tool_map = { + 'generate_code': generate_code, + 'refactor_code': refactor_code, + 'review_code': review_code, + 'organize_code': organize_code, + 'classify_code': classify_code, + 'analyze_project': analyze_project, + 'suggest_folder_structure': suggest_folder_structure, + 'create_code_file': create_code_file, + } + + if tool_name not in tool_map: + raise ValueError(f"未知的工具: {tool_name}") + + tool_func = tool_map[tool_name] + # 过滤 None 值 + filtered_params = {k: v for k, v in params.items() if v is not None} + return await tool_func(**filtered_params) + finally: + # 恢复原来的 API key + if api_key and old_api_key is not None: + os.environ['OPENAI_API_KEY'] = old_api_key + elif api_key: + # 如果原来没有设置,删除 + os.environ.pop('OPENAI_API_KEY', None) + + +# ==================== API 端点 ==================== + +@app.get("/") +async def root(): + """根路径,返回 API 信息""" + return { + "name": SERVER_NAME, + "version": "1.0.0", + "status": "running", + "endpoints": { + "health": "/health", + "docs": "/docs", + "api": f"/api/{API_VERSION}", + "mcp": "/mcp", + "mcp_sse": "/mcp/sse" + } + } + + +@app.get("/health") +async def health_check(): + """健康检查""" + return { + "status": "healthy", + "service": SERVER_NAME + } + + +# ==================== MCP 端点 ==================== + +# Session 管理 +sessions: Dict[str, Dict[str, Any]] = {} + + +async def handle_mcp_request(request_data: Dict[str, Any], session_id: Optional[str] = None, api_key: Optional[str] = None) -> Dict[str, Any]: + """处理 MCP 请求""" + method = request_data.get("method") + params = request_data.get("params", {}) + request_id = request_data.get("id") + + # 对于 tools/call 方法,需要验证 API key + if method == "tools/call": + if not api_key or api_key.strip() == "" or api_key.strip() == "sk": + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32001, + "message": "缺少 API key。请在请求头中提供 'api-key' 或 'Authorization: Bearer '。" + } + } + + try: + if method == "initialize": + # 初始化会话 + if not session_id: + session_id = str(uuid.uuid4()) + sessions[session_id] = { + "initialized": True, + "capabilities": {} + } + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {}, + "resources": {} + }, + "serverInfo": { + "name": "代码助手 Agent", + "version": "1.0.0" + } + } + } + + elif method == "tools/list": + # 列出所有工具 + tools = [ + { + "name": "organize_code", + "description": "智能分析代码并自动组织到合适的文件夹中", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "要组织的代码内容"}, + "code_type": {"type": "string", "description": "代码类型(可选)"}, + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."} + }, + "required": ["code_content"] + } + }, + { + "name": "suggest_folder_structure", + "description": "根据项目描述,建议合理的文件夹结构", + "inputSchema": { + "type": "object", + "properties": { + "project_description": {"type": "string", "description": "项目描述"} + }, + "required": ["project_description"] + } + }, + { + "name": "classify_code", + "description": "分析代码内容,确定其应该属于哪个类别/文件夹", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "代码内容"} + }, + "required": ["code_content"] + } + }, + { + "name": "generate_code", + "description": "根据需求生成代码", + "inputSchema": { + "type": "object", + "properties": { + "requirement": {"type": "string", "description": "代码需求描述"}, + "language": {"type": "string", "description": "编程语言", "default": "python"}, + "style": {"type": "string", "description": "代码风格(可选)"}, + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."} + }, + "required": ["requirement"] + } + }, + { + "name": "refactor_code", + "description": "重构代码,改进代码质量、性能和可维护性", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "要重构的代码"}, + "refactoring_goal": {"type": "string", "description": "重构目标(可选)"} + }, + "required": ["code_content"] + } + }, + { + "name": "review_code", + "description": "审查代码,发现潜在问题、bug 和改进建议", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "要审查的代码"}, + "file_path": {"type": "string", "description": "文件路径(可选)"} + }, + "required": ["code_content"] + } + }, + { + "name": "analyze_project", + "description": "分析项目结构,提供项目概览和建议", + "inputSchema": { + "type": "object", + "properties": { + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."}, + "max_depth": {"type": "integer", "description": "最大扫描深度", "default": 3} + } + } + }, + { + "name": "create_code_file", + "description": "在指定文件夹中创建代码文件", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "代码内容"}, + "folder_path": {"type": "string", "description": "目标文件夹路径"}, + "file_name": {"type": "string", "description": "文件名"}, + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."} + }, + "required": ["code_content", "folder_path", "file_name"] + } + } + ] + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": tools + } + } + + elif method == "tools/call": + # 调用工具 + tool_name = params.get("name") + arguments = params.get("arguments", {}) + + result = await call_mcp_tool(tool_name, arguments, api_key=api_key) + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + { + "type": "text", + "text": str(result) + } + ] + } + } + + elif method == "ping": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {} + } + + else: + raise ValueError(f"Unknown method: {method}") + + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": str(e) + } + } + + +@app.post("/mcp") +async def mcp_http_endpoint(request: Request): + """MCP HTTP 端点 - Streamable HTTP""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") + + # 从请求头获取 API key + api_key = request.headers.get("api-key") or request.headers.get("api_key") + if not api_key: + auth_header = request.headers.get("Authorization") + if auth_header: + if auth_header.startswith("Bearer "): + api_key = auth_header[7:] + else: + api_key = auth_header + + response = await handle_mcp_request(body, session_id, api_key=api_key) + + # 如果创建了新会话,返回 session ID + if "result" in response and isinstance(response["result"], dict): + if "sessionId" not in response["result"] and session_id: + response["result"]["sessionId"] = session_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": f"Parse error: {str(e)}" + } + } + ) + + +@app.get("/mcp/sse") +async def mcp_sse_endpoint(request: Request): + """MCP SSE 端点 - Server-Sent Events""" + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def event_stream() -> AsyncGenerator[str, None]: + # 发送初始连接消息 + yield f"data: {json.dumps({'type': 'connection', 'sessionId': session_id})}\n\n" + + # 保持连接 + while True: + await asyncio.sleep(30) # 心跳 + yield f"data: {json.dumps({'type': 'ping'})}\n\n" + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + +@app.post(f"/api/{API_VERSION}/generate-code", response_model=APIResponse) +async def api_generate_code(request: GenerateCodeRequest, api_key: str = Depends(verify_api_key)): + """ + 生成代码 + + 根据自然语言需求生成高质量的代码 + """ + try: + result = await call_mcp_tool('generate_code', { + 'requirement': request.requirement, + 'language': request.language, + 'style': request.style, + 'project_root': request.project_root + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="代码生成成功" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"代码生成失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/refactor-code", response_model=APIResponse) +async def api_refactor_code(request: RefactorCodeRequest, api_key: str = Depends(verify_api_key)): + """ + 重构代码 + + 改进代码质量、性能和可维护性 + """ + try: + result = await call_mcp_tool('refactor_code', { + 'code_content': request.code_content, + 'refactoring_goal': request.refactoring_goal + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="代码重构成功" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"代码重构失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/review-code", response_model=APIResponse) +async def api_review_code(request: ReviewCodeRequest, api_key: str = Depends(verify_api_key)): + """ + 审查代码 + + 发现潜在问题、bug 和改进建议 + """ + try: + result = await call_mcp_tool('review_code', { + 'code_content': request.code_content, + 'file_path': request.file_path + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="代码审查完成" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"代码审查失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/organize-code", response_model=APIResponse) +async def api_organize_code(request: OrganizeCodeRequest, api_key: str = Depends(verify_api_key)): + """ + 组织代码 + + 智能分析代码并自动组织到合适的文件夹中 + """ + try: + result = await call_mcp_tool('organize_code', { + 'code_content': request.code_content, + 'code_type': request.code_type, + 'project_root': request.project_root + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="代码组织成功" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"代码组织失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/classify-code", response_model=APIResponse) +async def api_classify_code(request: ClassifyCodeRequest, api_key: str = Depends(verify_api_key)): + """ + 分类代码 + + 分析代码内容,确定其应该属于哪个类别/文件夹 + """ + try: + result = await call_mcp_tool('classify_code', { + 'code_content': request.code_content + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="代码分类完成" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"代码分类失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/analyze-project", response_model=APIResponse) +async def api_analyze_project(request: AnalyzeProjectRequest, api_key: str = Depends(verify_api_key)): + """ + 分析项目 + + 分析项目结构,提供项目概览和改进建议 + """ + try: + result = await call_mcp_tool('analyze_project', { + 'project_root': request.project_root, + 'max_depth': request.max_depth + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="项目分析完成" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"项目分析失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/suggest-structure", response_model=APIResponse) +async def api_suggest_structure(request: SuggestStructureRequest, api_key: str = Depends(verify_api_key)): + """ + 建议项目结构 + + 根据项目描述,建议合理的文件夹结构 + """ + try: + result = await call_mcp_tool('suggest_folder_structure', { + 'project_description': request.project_description + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="结构建议生成成功" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"结构建议生成失败: {str(e)}" + ) + + +@app.post(f"/api/{API_VERSION}/create-file", response_model=APIResponse) +async def api_create_file(request: CreateFileRequest, api_key: str = Depends(verify_api_key)): + """ + 创建代码文件 + + 在指定文件夹中创建代码文件 + """ + try: + result = await call_mcp_tool('create_code_file', { + 'code_content': request.code_content, + 'folder_path': request.folder_path, + 'file_name': request.file_name, + 'project_root': request.project_root + }, api_key=api_key) + return APIResponse( + success=True, + data={"result": result}, + message="文件创建成功" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"文件创建失败: {str(e)}" + ) + + +if __name__ == '__main__': + import uvicorn + + # 从环境变量读取配置 + host = os.getenv('API_HOST', '0.0.0.0') + port = int(os.getenv('API_PORT', '8000')) + + print(f"🚀 启动 {SERVER_NAME}") + print(f"📡 监听地址: http://{host}:{port}") + print(f"📚 API 文档: http://{host}:{port}/docs") + + uvicorn.run( + app, + host=host, + port=port, + log_level="info" + ) diff --git a/agent_templates/agents/code_ai_agent/src/server/mcp_http_server.py b/agent_templates/agents/code_ai_agent/src/server/mcp_http_server.py new file mode 100644 index 0000000..6dceb8e --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/server/mcp_http_server.py @@ -0,0 +1,402 @@ +""" +MCP HTTP/SSE 服务器 - 支持远程调用 +实现 MCP 协议的 HTTP 和 SSE 传输方式,供 Cursor 等客户端远程调用 +""" +import json +import os +import uuid +from typing import Dict, Any, Optional, AsyncGenerator +from fastapi import FastAPI, Request, HTTPException, Header +from fastapi.responses import StreamingResponse, JSONResponse +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel + +# 导入 MCP 服务器和工具函数 +from .mcp_server import ( + server as mcp_server, + organize_code, + suggest_folder_structure, + classify_code, + generate_code, + refactor_code, + review_code, + analyze_project, + create_code_file +) + +# 工具映射 +TOOL_MAP = { + 'organize_code': organize_code, + 'suggest_folder_structure': suggest_folder_structure, + 'classify_code': classify_code, + 'generate_code': generate_code, + 'refactor_code': refactor_code, + 'review_code': review_code, + 'analyze_project': analyze_project, + 'create_code_file': create_code_file, +} + +# 创建 FastAPI 应用 +app = FastAPI( + title="MCP HTTP/SSE Server", + description="MCP 协议的 HTTP 和 SSE 传输实现", + version="1.0.0" +) + +# 配置 CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Session 管理 +sessions: Dict[str, Dict[str, Any]] = {} + + +class MCPRequest(BaseModel): + """MCP JSON-RPC 请求""" + jsonrpc: str = "2.0" + id: Optional[str] = None + method: str + params: Optional[Dict[str, Any]] = None + + +class MCPResponse(BaseModel): + """MCP JSON-RPC 响应""" + jsonrpc: str = "2.0" + id: Optional[str] = None + result: Optional[Any] = None + error: Optional[Dict[str, Any]] = None + + +async def handle_mcp_request(request_data: Dict[str, Any], session_id: Optional[str] = None) -> Dict[str, Any]: + """处理 MCP 请求""" + method = request_data.get("method") + params = request_data.get("params", {}) + request_id = request_data.get("id") + + try: + if method == "initialize": + # 初始化会话 + if not session_id: + session_id = str(uuid.uuid4()) + sessions[session_id] = { + "initialized": True, + "capabilities": {} + } + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {}, + "resources": {} + }, + "serverInfo": { + "name": "代码助手 Agent", + "version": "1.0.0" + } + } + } + + elif method == "tools/list": + # 列出所有工具 + tools = [ + { + "name": "organize_code", + "description": "智能分析代码并自动组织到合适的文件夹中", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "要组织的代码内容"}, + "code_type": {"type": "string", "description": "代码类型(可选)"}, + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."} + }, + "required": ["code_content"] + } + }, + { + "name": "suggest_folder_structure", + "description": "根据项目描述,建议合理的文件夹结构", + "inputSchema": { + "type": "object", + "properties": { + "project_description": {"type": "string", "description": "项目描述"} + }, + "required": ["project_description"] + } + }, + { + "name": "classify_code", + "description": "分析代码内容,确定其应该属于哪个类别/文件夹", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "代码内容"} + }, + "required": ["code_content"] + } + }, + { + "name": "generate_code", + "description": "根据需求生成代码", + "inputSchema": { + "type": "object", + "properties": { + "requirement": {"type": "string", "description": "代码需求描述"}, + "language": {"type": "string", "description": "编程语言", "default": "python"}, + "style": {"type": "string", "description": "代码风格(可选)"}, + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."} + }, + "required": ["requirement"] + } + }, + { + "name": "refactor_code", + "description": "重构代码,改进代码质量、性能和可维护性", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "要重构的代码"}, + "refactoring_goal": {"type": "string", "description": "重构目标(可选)"} + }, + "required": ["code_content"] + } + }, + { + "name": "review_code", + "description": "审查代码,发现潜在问题、bug 和改进建议", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "要审查的代码"}, + "file_path": {"type": "string", "description": "文件路径(可选)"} + }, + "required": ["code_content"] + } + }, + { + "name": "analyze_project", + "description": "分析项目结构,提供项目概览和建议", + "inputSchema": { + "type": "object", + "properties": { + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."}, + "max_depth": {"type": "integer", "description": "最大扫描深度", "default": 3} + } + } + }, + { + "name": "create_code_file", + "description": "在指定文件夹中创建代码文件", + "inputSchema": { + "type": "object", + "properties": { + "code_content": {"type": "string", "description": "代码内容"}, + "folder_path": {"type": "string", "description": "目标文件夹路径"}, + "file_name": {"type": "string", "description": "文件名"}, + "project_root": {"type": "string", "description": "项目根目录路径", "default": "."} + }, + "required": ["code_content", "folder_path", "file_name"] + } + } + ] + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": tools + } + } + + elif method == "tools/call": + # 调用工具 + tool_name = params.get("name") + arguments = params.get("arguments", {}) + + if tool_name not in TOOL_MAP: + raise ValueError(f"Tool '{tool_name}' not found") + + # 获取工具函数 + tool_func = TOOL_MAP[tool_name] + + # 调用工具(异步) + result = await tool_func(**arguments) + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + { + "type": "text", + "text": str(result) + } + ] + } + } + + elif method == "ping": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {} + } + + else: + raise ValueError(f"Unknown method: {method}") + + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": str(e) + } + } + + +@app.post("/mcp") +async def mcp_http_endpoint(request: Request): + """MCP HTTP 端点 - Streamable HTTP""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") + + response = await handle_mcp_request(body, session_id) + + # 如果创建了新会话,返回 session ID + if "result" in response and isinstance(response["result"], dict): + if "sessionId" not in response["result"] and session_id: + response["result"]["sessionId"] = session_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": f"Parse error: {str(e)}" + } + } + ) + + +@app.get("/mcp/sse") +async def mcp_sse_endpoint(request: Request): + """MCP SSE 端点 - Server-Sent Events""" + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def event_stream() -> AsyncGenerator[str, None]: + # 发送初始连接消息 + yield f"data: {json.dumps({'type': 'connection', 'sessionId': session_id})}\n\n" + + # 保持连接,等待请求 + # 注意:SSE 通常需要客户端通过 POST 发送请求 + # 这里简化实现,实际应该使用 WebSocket 或轮询 + + # 发送工具列表 + tools = list(TOOL_MAP.keys()) + yield f"data: {json.dumps({'type': 'tools', 'tools': tools})}\n\n" + + # 保持连接 + import asyncio + while True: + await asyncio.sleep(30) # 心跳 + yield f"data: {json.dumps({'type': 'ping'})}\n\n" + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + +@app.post("/mcp/sse") +async def mcp_sse_post(request: Request): + """MCP SSE POST 端点 - 处理 SSE 请求""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def response_stream() -> AsyncGenerator[str, None]: + response = await handle_mcp_request(body, session_id) + yield f"data: {json.dumps(response)}\n\n" + + return StreamingResponse( + response_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + except Exception as e: + return JSONResponse( + status_code=400, + content={ + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": f"Parse error: {str(e)}" + } + } + ) + + +@app.get("/health") +async def health_check(): + """健康检查""" + return { + "status": "healthy", + "service": "MCP HTTP/SSE Server", + "tools_count": len(TOOL_MAP) + } + + +@app.get("/") +async def root(): + """根端点""" + return { + "service": "MCP HTTP/SSE Server", + "version": "1.0.0", + "endpoints": { + "mcp_http": "/mcp", + "mcp_sse": "/mcp/sse", + "health": "/health" + }, + "tools": list(TOOL_MAP.keys()) + } + + +if __name__ == "__main__": + import uvicorn + host = os.getenv("MCP_HOST", "0.0.0.0") + port = int(os.getenv("MCP_PORT", "8001")) + + print(f"🚀 MCP HTTP/SSE Server 启动中...") + print(f"📡 HTTP 端点: http://{host}:{port}/mcp") + print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse") + print(f"📚 健康检查: http://{host}:{port}/health") + + uvicorn.run(app, host=host, port=port) + diff --git a/agent_templates/agents/code_ai_agent/src/server/mcp_server.py b/agent_templates/agents/code_ai_agent/src/server/mcp_server.py new file mode 100644 index 0000000..901061f --- /dev/null +++ b/agent_templates/agents/code_ai_agent/src/server/mcp_server.py @@ -0,0 +1,654 @@ +""" +MCP 服务器 - 代码助手 Agent +使用 Pydantic AI 和 litellm gateway 提供专业的代码助手功能 +""" +import ast +import json +import os +import re +from pathlib import Path +from typing import Optional, List, Dict + +from mcp.server.fastmcp import FastMCP +from pydantic_ai import Agent + +# 配置 litellm gateway +# 从环境变量读取配置,支持云部署 +_DEFAULT_API_KEY = os.getenv('OPENAI_API_KEY', 'sk') +_DEFAULT_BASE_URL = os.getenv('OPENAI_BASE_URL', 'https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1') + +# 设置默认值(仅用于 MCP 服务器初始化,实际调用时会使用动态传入的 API key) +os.environ.setdefault('OPENAI_API_KEY', _DEFAULT_API_KEY) +os.environ.setdefault('OPENAI_BASE_URL', _DEFAULT_BASE_URL) + +# 创建 MCP 服务器 +server = FastMCP('代码助手 Agent') + +# 创建 Pydantic AI Agent +# 使用 litellm gateway,模型名称:taiji/gpt-4o-mini +# 可以通过环境变量 LITELLM_MODEL 自定义模型名称 +# pydantic_ai 要求模型名称格式为 provider:model_name (例如 openai:taiji/gpt-4o-mini) +def ensure_model_prefix(model_name: str) -> str: + """确保模型名称有 openai: 前缀""" + if not model_name: + return 'openai:taiji/gpt-4o-mini' + # 如果已经有 provider: 前缀,直接返回 + if ':' in model_name: + return model_name + # 否则添加 openai: 前缀 + return f'openai:{model_name}' + +model_name = ensure_model_prefix(os.getenv('LITELLM_MODEL', 'taiji/gpt-4o-mini')) + +# 用于存储当前请求的 API key(线程安全) +import contextvars +_current_api_key: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar('current_api_key', default=None) + +def get_current_api_key() -> str: + """获取当前请求的 API key,如果没有则使用默认值""" + api_key = _current_api_key.get() + return api_key if api_key else _DEFAULT_API_KEY + +def set_current_api_key(api_key: str): + """设置当前请求的 API key""" + _current_api_key.set(api_key) + +def create_agent_with_api_key(api_key: Optional[str] = None) -> Agent: + """创建使用指定 API key 的 Agent 实例""" + # 临时设置环境变量 + if api_key: + os.environ['OPENAI_API_KEY'] = api_key + + agent = Agent( + model_name, + system_prompt=CODE_ASSISTANT_SYSTEM_PROMPT + ) + return agent + +# 系统提示词(提取为常量) +CODE_ASSISTANT_SYSTEM_PROMPT = '''你是一个专业的代码助手,具备以下能力: + +1. **代码分析与理解**:深入分析代码的功能、结构和设计模式 +2. **代码组织**:根据代码职责和项目结构,智能分类和组织代码 +3. **代码生成**:根据需求生成高质量、符合最佳实践的代码 +4. **代码重构**:改进代码质量、性能和可维护性 +5. **代码审查**:发现潜在问题、bug 和改进建议 +6. **项目规划**:设计合理的项目结构和架构 + +**代码分类标准**: +- utils/helpers: 工具函数和辅助函数 +- models/schemas: 数据模型和模式定义 +- services: 业务逻辑服务层 +- controllers/handlers: 请求处理层 +- middleware: 中间件 +- config: 配置文件 +- tests: 测试代码 +- api/routes: API 路由 +- database: 数据库相关代码 +- auth: 认证授权相关 +- validators: 验证器 +- exceptions: 异常处理 +- constants: 常量定义 +- types: 类型定义 +- decorators: 装饰器 +- factories: 工厂模式相关 + +**响应格式要求**: +当需要返回结构化信息(如文件夹路径、文件名)时,请使用 JSON 格式: +{ + "folder_path": "utils", + "file_name": "helpers.py", + "reason": "这是工具函数,应该放在 utils 文件夹" +} + +请始终提供专业、准确、实用的建议。''' + +# 默认 Agent(使用默认 API key,仅用于 MCP 服务器) +code_assistant_agent = Agent( + model_name, + system_prompt=CODE_ASSISTANT_SYSTEM_PROMPT +) + + +def get_agent() -> Agent: + """获取使用当前环境变量 API key 的 Agent 实例 + + 每次调用都会创建新的 Agent 实例,以确保使用最新的 OPENAI_API_KEY 环境变量 + """ + return Agent( + model_name, + system_prompt=CODE_ASSISTANT_SYSTEM_PROMPT + ) + + +def _parse_ai_response(response: str, code_content: str = '') -> Dict[str, str]: + """解析 AI 响应,提取结构化信息""" + # 尝试提取 JSON + json_match = re.search(r'\{[^{}]*"folder_path"[^{}]*\}', response, re.DOTALL) + if json_match: + try: + return json.loads(json_match.group()) + except: + pass + + # 尝试提取文件夹路径和文件名 + folder_path = None + file_name = None + + # 查找文件夹路径 + folder_patterns = [ + r'文件夹[路径]*[::]\s*([^\n]+)', + r'folder[_\s]*path[::]\s*([^\n]+)', + r'路径[::]\s*([^\n]+)', + ] + for pattern in folder_patterns: + match = re.search(pattern, response, re.IGNORECASE) + if match: + folder_path = match.group(1).strip().strip('"\'`') + break + + # 查找文件名 + file_patterns = [ + r'文件[名]*[::]\s*([^\n]+)', + r'file[_\s]*name[::]\s*([^\n]+)', + r'文件名[::]\s*([^\n]+)', + ] + for pattern in file_patterns: + match = re.search(pattern, response, re.IGNORECASE) + if match: + file_name = match.group(1).strip().strip('"\'`') + break + + # 如果没找到,尝试从响应中推断 + if not folder_path: + response_lower = response.lower() + if 'utils' in response_lower or 'helper' in response_lower: + folder_path = 'utils' + elif 'model' in response_lower or 'schema' in response_lower: + folder_path = 'models' + elif 'service' in response_lower: + folder_path = 'services' + elif 'controller' in response_lower or 'handler' in response_lower: + folder_path = 'controllers' + elif 'middleware' in response_lower: + folder_path = 'middleware' + elif 'config' in response_lower: + folder_path = 'config' + elif 'test' in response_lower: + folder_path = 'tests' + elif 'api' in response_lower or 'route' in response_lower: + folder_path = 'api' + elif 'database' in response_lower or 'db' in response_lower: + folder_path = 'database' + elif 'auth' in response_lower: + folder_path = 'auth' + elif 'validator' in response_lower: + folder_path = 'validators' + elif 'exception' in response_lower: + folder_path = 'exceptions' + elif 'constant' in response_lower: + folder_path = 'constants' + else: + folder_path = 'utils' # 默认 + + if not file_name and code_content: + # 尝试从代码中提取类名或函数名 + try: + tree = ast.parse(code_content) + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef): + file_name = f"{node.name.lower()}.py" + break + elif isinstance(node, ast.FunctionDef) and not file_name: + file_name = f"{node.name.lower()}.py" + except: + file_name = 'code.py' + + if not file_name: + file_name = 'code.py' + + return { + 'folder_path': folder_path, + 'file_name': file_name, + 'reason': response + } + + +@server.tool() +async def organize_code( + code_content: str, + code_type: Optional[str] = None, + project_root: str = '.' +) -> str: + """ + 智能分析代码并自动组织到合适的文件夹中 + + Args: + code_content: 要组织的代码内容 + code_type: 代码类型(可选),如 'utils', 'models', 'services' 等 + project_root: 项目根目录路径,默认为当前目录 + + Returns: + 组织结果和文件保存路径 + """ + # 使用 Agent 分析代码并确定最佳文件夹 + if code_type: + prompt = f'''请分析以下代码,确定它应该放在哪个文件夹中。 +建议的类型是:{code_type} + +代码内容: +```python +{code_content} +``` + +请以 JSON 格式返回: +{{ + "folder_path": "文件夹路径(如 utils, models, services)", + "file_name": "建议的文件名(如 helpers.py)", + "reason": "简要说明为什么选择这个位置" +}}''' + else: + prompt = f'''请分析以下代码,确定它应该放在哪个文件夹中。 + +代码内容: +```python +{code_content} +``` + +请以 JSON 格式返回: +{{ + "folder_path": "文件夹路径(如 utils, models, services)", + "file_name": "建议的文件名(如 helpers.py)", + "reason": "简要说明为什么选择这个位置" +}}''' + + result = await get_agent().run(prompt) + response = result.output + + # 智能解析 AI 响应 + parsed = _parse_ai_response(response, code_content) + folder_path = parsed.get('folder_path', 'utils') + file_name = parsed.get('file_name', 'code.py') + reason = parsed.get('reason', response) + + # 确保文件名有 .py 扩展名 + if not file_name.endswith('.py'): + file_name += '.py' + + # 创建完整路径 + full_folder_path = os.path.join(project_root, folder_path) + Path(full_folder_path).mkdir(parents=True, exist_ok=True) + + # 保存代码到文件 + file_path = os.path.join(full_folder_path, file_name) + + # 如果文件已存在,添加序号 + if os.path.exists(file_path): + base_name = file_name[:-3] + counter = 1 + while os.path.exists(file_path): + file_name = f"{base_name}_{counter}.py" + file_path = os.path.join(full_folder_path, file_name) + counter += 1 + + with open(file_path, 'w', encoding='utf-8') as f: + f.write(code_content) + + return f'''✅ 代码已成功组织! + +📁 文件夹路径: {full_folder_path} +📄 文件名: {file_name} +💾 完整路径: {file_path} + +🤖 AI 分析: +{reason}''' + + +@server.tool() +async def suggest_folder_structure(project_description: str) -> str: + """ + 根据项目描述,建议合理的文件夹结构 + + Args: + project_description: 项目描述 + + Returns: + 建议的文件夹结构 + """ + prompt = f'''根据以下项目描述,建议一个合理的文件夹结构: + +项目描述:{project_description} + +请提供: +1. 推荐的文件夹结构(树状图) +2. 每个文件夹的用途说明 +3. 文件夹之间的依赖关系''' + + result = await get_agent().run(prompt) + return result.output + + +@server.tool() +async def classify_code(code_content: str) -> str: + """ + 分析代码内容,确定其应该属于哪个类别/文件夹 + + Args: + code_content: 代码内容 + + Returns: + 代码分类建议 + """ + prompt = f'''请分析以下代码,确定它的功能和应该属于的类别: + +代码内容: +```python +{code_content} +``` + +请提供: +1. 代码的主要功能 +2. 建议的文件夹分类 +3. 推荐的文件名 +4. 是否需要其他相关文件''' + + result = await get_agent().run(prompt) + return result.output + + +@server.tool() +async def generate_code( + requirement: str, + language: str = 'python', + style: Optional[str] = None, + project_root: str = '.' +) -> str: + """ + 根据需求生成代码 + + Args: + requirement: 代码需求描述 + language: 编程语言,默认为 'python' + style: 代码风格(可选),如 'fastapi', 'django', 'flask' 等 + project_root: 项目根目录路径,默认为当前目录 + + Returns: + 生成的代码和保存路径 + """ + prompt = f'''请根据以下需求生成高质量的 {language} 代码: + +需求:{requirement} +{f"代码风格:{style}" if style else ""} + +要求: +1. 代码要符合最佳实践 +2. 包含适当的注释和文档字符串 +3. 遵循 PEP 8(如果是 Python) +4. 包含错误处理 +5. 代码要完整、可运行 + +请生成代码,并在代码后说明: +1. 代码的主要功能 +2. 建议保存的文件夹路径 +3. 建议的文件名''' + + result = await get_agent().run(prompt) + response = result.output + + # 提取代码块 + code_match = re.search(r'```(?:python|py)?\n(.*?)```', response, re.DOTALL) + if code_match: + generated_code = code_match.group(1).strip() + else: + # 如果没有代码块,尝试提取整个响应 + generated_code = response + + # 解析建议的保存位置 + parsed = _parse_ai_response(response, generated_code) + folder_path = parsed.get('folder_path', 'generated') + file_name = parsed.get('file_name', 'generated_code.py') + + # 确保文件名有正确的扩展名 + if language == 'python' and not file_name.endswith('.py'): + file_name += '.py' + + # 保存代码 + full_folder_path = os.path.join(project_root, folder_path) + Path(full_folder_path).mkdir(parents=True, exist_ok=True) + + file_path = os.path.join(full_folder_path, file_name) + if os.path.exists(file_path): + base_name = file_name[:-3] if file_name.endswith('.py') else file_name + counter = 1 + while os.path.exists(file_path): + new_name = f"{base_name}_{counter}.py" if file_name.endswith('.py') else f"{base_name}_{counter}" + file_path = os.path.join(full_folder_path, new_name) + counter += 1 + file_name = os.path.basename(file_path) + + with open(file_path, 'w', encoding='utf-8') as f: + f.write(generated_code) + + return f'''✅ 代码生成成功! + +📁 文件夹: {full_folder_path} +📄 文件名: {file_name} +💾 完整路径: {file_path} + +📝 生成的代码: +```{language} +{generated_code} +``` + +💡 AI 说明: +{response}''' + + +@server.tool() +async def refactor_code( + code_content: str, + refactoring_goal: Optional[str] = None +) -> str: + """ + 重构代码,改进代码质量、性能和可维护性 + + Args: + code_content: 要重构的代码 + refactoring_goal: 重构目标(可选),如 'improve performance', 'add error handling' 等 + + Returns: + 重构后的代码和改进说明 + """ + goal_text = f"\n重构目标:{refactoring_goal}" if refactoring_goal else "" + + prompt = f'''请重构以下代码,改进代码质量、性能和可维护性:{goal_text} + +原始代码: +```python +{code_content} +``` + +请: +1. 分析代码的问题和改进点 +2. 提供重构后的代码 +3. 说明做了哪些改进 +4. 如果可能,提供性能对比或改进建议''' + + result = await get_agent().run(prompt) + response = result.output + + # 提取重构后的代码 + code_match = re.search(r'```(?:python|py)?\n(.*?)```', response, re.DOTALL) + if code_match: + refactored_code = code_match.group(1).strip() + else: + refactored_code = code_content # 如果没有找到,返回原代码 + + return f'''✅ 代码重构完成! + +📝 重构后的代码: +```python +{refactored_code} +``` + +💡 重构说明: +{response}''' + + +@server.tool() +async def review_code( + code_content: str, + file_path: Optional[str] = None +) -> str: + """ + 审查代码,发现潜在问题、bug 和改进建议 + + Args: + code_content: 要审查的代码 + file_path: 文件路径(可选),用于上下文 + + Returns: + 代码审查报告 + """ + file_context = f"\n文件路径:{file_path}" if file_path else "" + + prompt = f'''请审查以下代码,提供详细的代码审查报告:{file_context} + +代码内容: +```python +{code_content} +``` + +请检查: +1. **潜在 Bug**:逻辑错误、边界条件、异常处理 +2. **代码质量**:可读性、可维护性、代码风格 +3. **性能问题**:性能瓶颈、优化建议 +4. **安全性**:安全漏洞、输入验证 +5. **最佳实践**:是否符合语言和框架的最佳实践 +6. **改进建议**:具体的改进方案 + +请以结构化的方式提供审查报告。''' + + result = await get_agent().run(prompt) + return result.output + + +@server.tool() +async def analyze_project( + project_root: str = '.', + max_depth: int = 3 +) -> str: + """ + 分析项目结构,提供项目概览和建议 + + Args: + project_root: 项目根目录路径,默认为当前目录 + max_depth: 最大扫描深度,默认为 3 + + Returns: + 项目分析报告 + """ + project_path = Path(project_root) + if not project_path.exists(): + return f"❌ 项目路径不存在:{project_root}" + + # 扫描项目结构 + structure = [] + python_files = [] + + for root, dirs, files in os.walk(project_path): + # 跳过隐藏文件夹和常见的不需要扫描的文件夹 + dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['__pycache__', 'node_modules', 'venv', '.venv']] + + level = root.replace(str(project_path), '').count(os.sep) + if level <= max_depth: + indent = ' ' * level + folder_name = os.path.basename(root) or project_path.name + structure.append(f"{indent}{folder_name}/") + + for file in files: + if file.endswith('.py'): + python_files.append(os.path.join(root, file)) + structure.append(f"{indent} {file}") + + structure_text = '\n'.join(structure[:50]) # 限制输出长度 + + # 统计信息 + total_py_files = len(python_files) + + prompt = f'''请分析以下项目结构,提供项目概览和改进建议: + +项目结构: +{structure_text} + +统计信息: +- Python 文件数量:{total_py_files} +- 项目根目录:{project_root} + +请提供: +1. **项目类型识别**:这是什么类型的项目(Web应用、API服务、库等) +2. **结构评估**:文件夹结构是否合理 +3. **改进建议**:如何优化项目结构 +4. **缺失组件**:可能缺失的重要组件或文件夹 +5. **依赖关系**:文件夹之间的依赖关系分析''' + + result = await get_agent().run(prompt) + + return f'''📊 项目分析报告 + +📁 项目路径: {project_root} +📄 Python 文件数: {total_py_files} + +📋 项目结构(前50项): +{structure_text} + +🤖 AI 分析: +{result.output}''' + + +@server.tool() +async def create_code_file( + code_content: str, + folder_path: str, + file_name: str, + project_root: str = '.' +) -> str: + """ + 在指定文件夹中创建代码文件 + + Args: + code_content: 代码内容 + folder_path: 目标文件夹路径(相对于项目根目录) + file_name: 文件名 + project_root: 项目根目录路径,默认为当前目录 + + Returns: + 创建结果 + """ + full_folder_path = os.path.join(project_root, folder_path) + Path(full_folder_path).mkdir(parents=True, exist_ok=True) + + file_path = os.path.join(full_folder_path, file_name) + + # 如果文件已存在,询问是否覆盖(这里直接覆盖,实际可以添加参数控制) + if os.path.exists(file_path): + backup_path = file_path + '.backup' + with open(backup_path, 'w', encoding='utf-8') as f: + with open(file_path, 'r', encoding='utf-8') as orig: + f.write(orig.read()) + + with open(file_path, 'w', encoding='utf-8') as f: + f.write(code_content) + + backup_msg = f"\n⚠️ 原文件已备份到: {backup_path}" if os.path.exists(file_path + '.backup') else "" + + return f'''✅ 文件创建成功! + +📁 文件夹: {full_folder_path} +📄 文件名: {file_name} +💾 完整路径: {file_path}{backup_msg}''' + + +if __name__ == '__main__': + server.run() + diff --git a/agent_templates/agents/code_ai_agent/tests/__init__.py b/agent_templates/agents/code_ai_agent/tests/__init__.py new file mode 100644 index 0000000..60f0865 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/tests/__init__.py @@ -0,0 +1,4 @@ +""" +测试模块 +""" + diff --git a/agent_templates/agents/code_ai_agent/tests/test_agent.py b/agent_templates/agents/code_ai_agent/tests/test_agent.py new file mode 100644 index 0000000..1f78d13 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/tests/test_agent.py @@ -0,0 +1,266 @@ +""" +测试代码助手 Agent 是否正常运行 +直接测试 Agent 功能,验证能否正常返回数据 +""" +import asyncio +import os +import sys +from pathlib import Path + +# 确保使用虚拟环境 +venv_path = Path(__file__).parent / '.venv' +if venv_path.exists(): + venv_python = venv_path / 'bin' / 'python' + if venv_python.exists(): + print(f"✅ 检测到虚拟环境: {venv_path}") + else: + print(f"⚠️ 虚拟环境存在但 Python 可执行文件未找到") + +# 设置环境变量 - 配置 litellm gateway +os.environ['OPENAI_API_KEY'] = 'sk-' +# 设置 litellm gateway 的 base URL +os.environ['OPENAI_BASE_URL'] = 'https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1' + +# 导入 Pydantic AI +try: + from pydantic_ai import Agent + print("✅ Pydantic AI 导入成功") +except ImportError as e: + print(f"❌ Pydantic AI 导入失败: {e}") + print("请运行: pip install -r requirements.txt") + sys.exit(1) + + +async def test_agent_basic(): + """测试 Agent 基本功能""" + print("\n" + "=" * 70) + print("测试 1: Agent 基本功能测试") + print("=" * 70) + + try: + # 创建 Agent + model_name = os.getenv('LITELLM_MODEL', 'openai:taiji/gpt-4o-mini') + print(f"📌 使用模型: {model_name}") + + agent = Agent( + model_name, + system_prompt='你是一个专业的代码助手。' + ) + + # 测试简单查询 + print("\n🤖 发送测试查询...") + result = await agent.run("请用一句话介绍你自己,并确认你能正常工作") + + print(f"\n✅ Agent 响应成功!") + print(f"📝 响应内容:\n{result.output}") + print(f"\n📊 响应统计:") + print(f" - 响应长度: {len(result.output)} 字符") + + return True + + except Exception as e: + print(f"\n❌ Agent 测试失败: {e}") + import traceback + traceback.print_exc() + return False + + +async def test_code_classification(): + """测试代码分类功能""" + print("\n" + "=" * 70) + print("测试 2: 代码分类功能测试") + print("=" * 70) + + try: + model_name = os.getenv('LITELLM_MODEL', 'openai:taiji/gpt-4o-mini') + agent = Agent( + model_name, + system_prompt='''你是一个专业的代码助手,擅长分析和分类代码。 +请根据代码功能,确定它应该属于哪个文件夹类别。''' + ) + + test_code = """ +def calculate_total(items): + \"\"\"计算商品总价\"\"\" + total = sum(item['price'] * item['quantity'] for item in items) + return total +""" + + print("\n📝 测试代码:") + print(test_code) + print("\n🤖 分析代码...") + + prompt = f'''请分析以下代码,确定它应该放在哪个文件夹中: + +代码内容: +```python +{test_code} +``` + +请提供: +1. 最适合的文件夹路径 +2. 建议的文件名 +3. 简要说明为什么选择这个位置''' + + result = await agent.run(prompt) + + print(f"\n✅ 代码分类成功!") + print(f"📝 AI 分析结果:\n{result.output}") + + return True + + except Exception as e: + print(f"\n❌ 代码分类测试失败: {e}") + import traceback + traceback.print_exc() + return False + + +async def test_code_generation(): + """测试代码生成功能""" + print("\n" + "=" * 70) + print("测试 3: 代码生成功能测试") + print("=" * 70) + + try: + model_name = os.getenv('LITELLM_MODEL', 'openai:taiji/gpt-4o-mini') + agent = Agent( + model_name, + system_prompt='''你是一个专业的代码助手,擅长生成高质量的代码。 +请根据需求生成符合最佳实践的代码。''' + ) + + requirement = "创建一个简单的工具函数,用于格式化日期字符串" + + print(f"\n📋 需求: {requirement}") + print("\n🤖 生成代码...") + + prompt = f'''请根据以下需求生成 Python 代码: + +需求:{requirement} + +要求: +1. 代码要符合最佳实践 +2. 包含适当的注释和文档字符串 +3. 遵循 PEP 8 +4. 包含错误处理 + +请生成代码:''' + + result = await agent.run(prompt) + + print(f"\n✅ 代码生成成功!") + print(f"📝 生成的代码:\n{result.output}") + + # 检查是否包含代码 + if 'def ' in result.output or 'import ' in result.output: + print("\n✅ 响应包含代码内容") + else: + print("\n⚠️ 响应可能不包含代码,请检查") + + return True + + except Exception as e: + print(f"\n❌ 代码生成测试失败: {e}") + import traceback + traceback.print_exc() + return False + + +async def test_code_review(): + """测试代码审查功能""" + print("\n" + "=" * 70) + print("测试 4: 代码审查功能测试") + print("=" * 70) + + try: + model_name = os.getenv('LITELLM_MODEL', 'openai:taiji/gpt-4o-mini') + agent = Agent( + model_name, + system_prompt='''你是一个专业的代码审查员,擅长发现代码问题。 +请仔细审查代码,发现潜在问题并提供改进建议。''' + ) + + code_to_review = """ +def get_user(id): + users = { + 1: {'name': 'Alice', 'age': 30}, + 2: {'name': 'Bob', 'age': 25} + } + return users[id] +""" + + print("\n📝 待审查代码:") + print(code_to_review) + print("\n🤖 审查代码...") + + prompt = f'''请审查以下代码,发现潜在问题: + +代码: +```python +{code_to_review} +``` + +请检查: +1. 潜在的 Bug +2. 代码质量问题 +3. 改进建议''' + + result = await agent.run(prompt) + + print(f"\n✅ 代码审查成功!") + print(f"📝 审查结果:\n{result.output}") + + return True + + except Exception as e: + print(f"\n❌ 代码审查测试失败: {e}") + import traceback + traceback.print_exc() + return False + + +async def main(): + """主测试函数""" + print("=" * 70) + print("🧪 代码助手 Agent 功能测试") + print("=" * 70) + + # 检查环境 + print(f"\n📋 环境信息:") + print(f" - Python 版本: {sys.version}") + print(f" - 工作目录: {os.getcwd()}") + print(f" - API Key: {os.environ.get('OPENAI_API_KEY', '未设置')[:20]}...") + print(f" - Base URL: {os.environ.get('OPENAI_BASE_URL', '未设置')}") + + # 运行测试 + results = [] + + results.append(await test_agent_basic()) + results.append(await test_code_classification()) + results.append(await test_code_generation()) + results.append(await test_code_review()) + + # 总结 + print("\n" + "=" * 70) + print("📊 测试结果总结") + print("=" * 70) + + passed = sum(results) + total = len(results) + + print(f"\n✅ 通过: {passed}/{total}") + print(f"❌ 失败: {total - passed}/{total}") + + if passed == total: + print("\n🎉 所有测试通过!Agent 运行正常!") + return 0 + else: + print("\n⚠️ 部分测试失败,请检查配置和网络连接") + return 1 + + +if __name__ == '__main__': + exit_code = asyncio.run(main()) + sys.exit(exit_code) + diff --git a/agent_templates/agents/code_ai_agent/tests/test_mcp_server.py b/agent_templates/agents/code_ai_agent/tests/test_mcp_server.py new file mode 100644 index 0000000..a0255a3 --- /dev/null +++ b/agent_templates/agents/code_ai_agent/tests/test_mcp_server.py @@ -0,0 +1,83 @@ +""" +快速测试 MCP 服务器配置 +验证 Agent 和工具是否正常工作 +""" +import asyncio +import os +import sys +from pathlib import Path + +# 添加项目根目录到 Python 路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from src.server.mcp_server import code_assistant_agent, organize_code, classify_code + + +async def test_agent(): + """测试 Agent 基本功能""" + print("测试 Agent 基本功能...") + print("-" * 60) + + try: + result = await code_assistant_agent.run("请用一句话介绍你自己") + print("✅ Agent 响应成功:") + print(result.output) + print() + return True + except Exception as e: + print(f"❌ Agent 测试失败: {e}") + print() + return False + + +async def test_classify_code(): + """测试代码分类功能""" + print("测试代码分类功能...") + print("-" * 60) + + test_code = """ +def calculate_sum(numbers): + return sum(numbers) +""" + + try: + result = await classify_code(test_code) + print("✅ 代码分类成功:") + print(result) + print() + return True + except Exception as e: + print(f"❌ 代码分类测试失败: {e}") + print() + return False + + +async def main(): + """主测试函数""" + print("=" * 60) + print("MCP 服务器配置测试") + print("=" * 60) + print() + + print(f"API Key: {os.environ.get('OPENAI_API_KEY', '未设置')[:20]}...") + print(f"Base URL: {os.environ.get('OPENAI_BASE_URL', '使用默认')}") + print() + + # 测试 Agent + agent_ok = await test_agent() + + # 测试工具 + tool_ok = await test_classify_code() + + print("=" * 60) + if agent_ok and tool_ok: + print("✅ 所有测试通过!") + else: + print("⚠️ 部分测试失败,请检查配置") + print("=" * 60) + + +if __name__ == '__main__': + asyncio.run(main()) + diff --git a/agent_templates/agents/facebook_agent/Dockerfile b/agent_templates/agents/facebook_agent/Dockerfile new file mode 100644 index 0000000..032eadd --- /dev/null +++ b/agent_templates/agents/facebook_agent/Dockerfile @@ -0,0 +1,37 @@ +# Dockerfile for Facebook Agent 服务 +FROM python:3.12-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONPATH=/app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements.txt ./requirements.txt + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY . . + +# 暴露端口 +# 8000: API 服务端口 +# 8001: MCP HTTP 服务端口 +EXPOSE 8000 8001 + +# 健康检查(默认检查8000端口) +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# 启动命令(默认启动API服务器) +CMD ["python", "run_api.py"] diff --git a/agent_templates/agents/facebook_agent/Dockerfile.mcp b/agent_templates/agents/facebook_agent/Dockerfile.mcp new file mode 100644 index 0000000..17163b7 --- /dev/null +++ b/agent_templates/agents/facebook_agent/Dockerfile.mcp @@ -0,0 +1,42 @@ +# Dockerfile for Facebook Agent 服务 +FROM python:3.12-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONPATH=/app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件(构建上下文是 ..,所以路径是 facebook_agent/requirements.txt) +COPY facebook_agent/requirements.txt ./requirements.txt + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码(构建上下文是 ..,所以从 facebook_agent/ 复制到 ./facebook_agent/) +# 这会将 aks_agent/facebook_agent/ 的内容复制到 /app/facebook_agent/ +COPY facebook_agent/ ./facebook_agent/ + +# 安装curl用于健康检查 +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +# 暴露端口 +# 8000: API 服务端口 +# 8001: MCP HTTP 服务端口 +EXPOSE 8000 8001 + +# 健康检查(默认检查8000端口,MCP服务会覆盖) +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# 启动命令(默认启动API服务器,可通过command覆盖) +CMD ["python", "-m", "uvicorn", "facebook_agent.api:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/agent_templates/agents/facebook_agent/__init__.py b/agent_templates/agents/facebook_agent/__init__.py new file mode 100644 index 0000000..ae13dde --- /dev/null +++ b/agent_templates/agents/facebook_agent/__init__.py @@ -0,0 +1,20 @@ +""" +Facebook搜索智能Agent +基于Pydantic AI框架,使用LiteLLM Gateway调用模型 +""" + +__version__ = "1.0.0" + +# 导出主要接口 +from .config import Config +from .agent import FacebookAgent +from .models.schemas import SearchRequest, SearchResponse, SearchResultItem + +__all__ = [ + "Config", + "FacebookAgent", + "SearchRequest", + "SearchResponse", + "SearchResultItem", +] + diff --git a/agent_templates/agents/facebook_agent/agent/__init__.py b/agent_templates/agents/facebook_agent/agent/__init__.py new file mode 100644 index 0000000..2113fcb --- /dev/null +++ b/agent_templates/agents/facebook_agent/agent/__init__.py @@ -0,0 +1,9 @@ +""" +Agent模块 +包含Facebook搜索Agent的核心逻辑 +""" + +from .facebook_agent import FacebookAgent, FacebookAgentDeps + +__all__ = ["FacebookAgent", "FacebookAgentDeps"] + diff --git a/agent_templates/agents/facebook_agent/agent/facebook_agent.py b/agent_templates/agents/facebook_agent/agent/facebook_agent.py new file mode 100644 index 0000000..2dbc4f8 --- /dev/null +++ b/agent_templates/agents/facebook_agent/agent/facebook_agent.py @@ -0,0 +1,100 @@ +""" +Facebook搜索智能Agent +基于Pydantic AI框架实现(简化版本,直接使用客户端) +""" + +from typing import List, Optional +from loguru import logger + +# 支持相对导入和绝对导入 +try: + from ..config import Config + from ..models.schemas import SearchRequest, SearchResponse, SearchResultItem + from ..clients.facebook_client import FacebookClient + from ..clients.litellm_client import LiteLLMClient +except ImportError: + # 如果相对导入失败,尝试绝对导入 + from config import Config + from models.schemas import SearchRequest, SearchResponse, SearchResultItem + from clients.facebook_client import FacebookClient + from clients.litellm_client import LiteLLMClient + + +# 定义Agent依赖类型 +class FacebookAgentDeps: + """Agent依赖项""" + + def __init__(self, config: Config): + self.config = config + self.facebook_client = FacebookClient(config) + self.llm_client = LiteLLMClient(config) + + +class FacebookAgent: + """Facebook搜索Agent包装类""" + + def __init__(self, config: Config): + """ + 初始化Agent + + Args: + config: 配置对象 + """ + self.config = config + self.deps = FacebookAgentDeps(config) + logger.info("FacebookAgent 初始化完成") + + async def search(self, request: SearchRequest) -> SearchResponse: + """ + 执行搜索 + + Args: + request: 搜索请求 + + Returns: + 搜索响应 + """ + try: + # 直接调用搜索工具获取结果 + search_results = await self.deps.facebook_client.search( + request.query, + request.limit + ) + + # 生成总结 + summary = None + if search_results: + try: + results_dict = [ + { + "title": r.title, + "snippet": r.snippet, + "author": r.author, + "likes": r.likes + } + for r in search_results + ] + summary = await self.deps.llm_client.generate_summary( + request.query, + results_dict + ) + except Exception as e: + logger.warning(f"生成总结失败: {e}") + + return SearchResponse( + success=True, + query=request.query, + results=search_results, + total_count=len(search_results), + summary=summary + ) + + except Exception as e: + logger.error(f"搜索过程出错: {e}") + return SearchResponse( + success=False, + query=request.query, + results=[], + message=f"搜索失败: {str(e)}" + ) + diff --git a/agent_templates/agents/facebook_agent/api.py b/agent_templates/agents/facebook_agent/api.py new file mode 100644 index 0000000..e4eaa87 --- /dev/null +++ b/agent_templates/agents/facebook_agent/api.py @@ -0,0 +1,548 @@ +""" +FastAPI服务 - Facebook搜索智能Agent +提供搜索API接口和MCP协议支持 +""" + +import json +import uuid +from typing import Dict, Any, Optional, AsyncGenerator +from fastapi import FastAPI, HTTPException, Request, Header, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse, JSONResponse +from pydantic import BaseModel, Field +from datetime import datetime +import sys +from typing import Optional +from loguru import logger + +# 支持相对导入和绝对导入 +try: + from .config import Config + from .agent import FacebookAgent + from .models.schemas import SearchRequest, SearchResponse + from .mcp_server import search_facebook, initialize_agent +except ImportError: + # 如果相对导入失败,尝试绝对导入 + from config import Config + from agent import FacebookAgent + from models.schemas import SearchRequest, SearchResponse + from mcp_server import search_facebook, initialize_agent + + +# ==================== FastAPI应用 ==================== + +# 创建FastAPI应用 +app = FastAPI( + title="Facebook搜索智能Agent API", + description="提供Facebook内容搜索服务和MCP协议支持,基于Pydantic AI框架和LiteLLM Gateway", + version="1.0.0" +) + +# 添加CORS中间件 +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 全局变量 +config: Optional[Config] = None +agent: Optional[FacebookAgent] = None + +# MCP 工具映射 +TOOL_MAP = { + 'search_facebook': search_facebook, +} + +# Session 管理 +sessions: Dict[str, Dict[str, Any]] = {} + + +class MCPRequest(BaseModel): + """MCP JSON-RPC 请求""" + jsonrpc: str = "2.0" + id: Optional[str] = None + method: str + params: Optional[Dict[str, Any]] = None + + +class MCPResponse(BaseModel): + """MCP JSON-RPC 响应""" + jsonrpc: str = "2.0" + id: Optional[str] = None + result: Optional[Any] = None + error: Optional[Dict[str, Any]] = None + + +async def handle_mcp_request(request_data: Dict[str, Any], session_id: Optional[str] = None, api_key: Optional[str] = None) -> Dict[str, Any]: + """处理 MCP 请求""" + method = request_data.get("method") + params = request_data.get("params", {}) + request_id = request_data.get("id") + + # 对于 tools/call 方法,需要验证 API key + if method == "tools/call": + if not api_key or api_key.strip() == "" or api_key.strip() == "sk": + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32001, + "message": "缺少 API key。请在请求头中提供 'api-key' 或 'Authorization: Bearer '。" + } + } + + try: + if method == "initialize": + # 初始化会话 + if not session_id: + session_id = str(uuid.uuid4()) + sessions[session_id] = { + "initialized": True, + "capabilities": {} + } + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {}, + "resources": {} + }, + "serverInfo": { + "name": "Facebook搜索Agent", + "version": "1.0.0" + } + } + } + + elif method == "tools/list": + # 列出所有工具 + tools = [ + { + "name": "search_facebook", + "description": "搜索Facebook内容,返回相关帖子和AI生成的总结。支持搜索关键词,返回帖子标题、链接、摘要、作者、点赞数等信息,并提供AI生成的总结。", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "搜索关键词,例如:technology news、travel tips、food recipes等" + }, + "limit": { + "type": "integer", + "description": "返回结果数量,默认5,最大20", + "default": 5, + "minimum": 1, + "maximum": 20 + } + }, + "required": ["query"] + } + } + ] + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": tools + } + } + + elif method == "tools/call": + # 调用工具 + tool_name = params.get("name") + arguments = params.get("arguments", {}) + + if tool_name not in TOOL_MAP: + raise ValueError(f"Tool '{tool_name}' not found") + + # 如果提供了 API key,临时更新配置 + if api_key and agent is not None: + old_api_key = agent.config.litellm_api_key + agent.config.litellm_api_key = api_key + # 重新创建 LLM 客户端以使用新的 API key + try: + from .clients.litellm_client import LiteLLMClient + except ImportError: + from clients.litellm_client import LiteLLMClient + agent.deps.llm_client = LiteLLMClient(agent.config) + + try: + # 获取工具函数 + tool_func = TOOL_MAP[tool_name] + + # 调用工具(异步) + result = await tool_func(**arguments) + finally: + # 恢复原来的 API key + if api_key and agent is not None and 'old_api_key' in locals(): + agent.config.litellm_api_key = old_api_key + try: + from .clients.litellm_client import LiteLLMClient + except ImportError: + from clients.litellm_client import LiteLLMClient + agent.deps.llm_client = LiteLLMClient(agent.config) + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + { + "type": "text", + "text": str(result) + } + ] + } + } + + elif method == "ping": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {} + } + + else: + raise ValueError(f"Unknown method: {method}") + + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": str(e) + } + } + + +def setup_logger(): + """配置日志""" + logger.remove() + logger.add( + sys.stderr, + level=config.log_level if config else "INFO", + format="{time:HH:mm:ss} | {level: <8} | {message}" + ) + + +@app.on_event("startup") +async def startup_event(): + """应用启动时初始化""" + global config, agent + + try: + # 加载配置 + config = Config.from_env() + config.validate() + + # 配置日志 + setup_logger() + + # 创建Agent + agent = FacebookAgent(config) + + logger.info("=" * 60) + logger.info("Facebook搜索智能Agent API 启动成功") + logger.info("=" * 60) + logger.info(f"LiteLLM Gateway: {config.litellm_gateway_url}") + logger.info(f"模型: {config.litellm_model}") + logger.info(f"Facebook API Host: {config.facebook_api_host}") + logger.info("MCP服务器已就绪,支持HTTP/SSE传输") + + # 初始化MCP Agent(如果还没有初始化) + try: + initialize_agent() + except Exception: + pass # 如果已经初始化,忽略错误 + + except Exception as e: + logger.error(f"启动失败: {e}") + raise + + +@app.get("/", tags=["健康检查"]) +async def root(): + """根路径 - 服务信息""" + return { + "service": "Facebook搜索智能Agent API", + "status": "running", + "version": "1.0.0", + "timestamp": datetime.now().isoformat(), + "endpoints": { + "api": { + "search": "/search", + "config": "/config", + "health": "/health" + }, + "mcp": { + "http": "/mcp", + "sse": "/mcp/sse" + } + }, + "tools": list(TOOL_MAP.keys()) + } + + +@app.get("/health", tags=["健康检查"]) +async def health_check(): + """健康检查接口""" + return { + "status": "healthy", + "service": "Facebook搜索智能Agent API + MCP Server", + "agent_initialized": agent is not None, + "tools_count": len(TOOL_MAP), + "timestamp": datetime.now().isoformat() + } + + +# ==================== API Key 验证 ==================== + +async def verify_api_key( + api_key: Optional[str] = Header(None, alias="api-key"), + authorization: Optional[str] = Header(None) +) -> str: + """ + 验证 API Key + + 支持从以下位置获取 API key: + 1. api-key 请求头 + 2. Authorization: Bearer 请求头 + + 如果没有提供 API key,返回 401 错误 + """ + # 从 api-key 请求头获取 + if api_key: + if not api_key.strip() or api_key.strip() == "sk": + raise HTTPException( + status_code=401, + detail="无效的 API key。请提供有效的 API key。" + ) + return api_key.strip() + + # 从 Authorization 请求头获取 + if authorization: + if authorization.startswith("Bearer "): + api_key = authorization[7:].strip() + else: + api_key = authorization.strip() + + if not api_key or api_key == "sk": + raise HTTPException( + status_code=401, + detail="无效的 API key。请提供有效的 API key。" + ) + return api_key + + # 如果没有提供 API key,返回错误 + raise HTTPException( + status_code=401, + detail="缺少 API key。请在请求头中提供 'api-key' 或 'Authorization: Bearer '。" + ) + + +@app.post("/search", response_model=SearchResponse, tags=["搜索"]) +async def search(request: SearchRequest, api_key: str = Depends(verify_api_key)): + """ + 执行Facebook搜索 + + - **query**: 搜索关键词(必填) + - **limit**: 返回结果数量(可选,默认5,最大20) + + 返回: + - 搜索结果列表 + - AI生成的总结 + - 统计信息 + """ + if agent is None: + raise HTTPException(status_code=503, detail="服务未初始化") + + try: + # 如果提供了 API key,临时更新配置 + if api_key: + old_api_key = agent.config.litellm_api_key + agent.config.litellm_api_key = api_key + # 重新创建 LLM 客户端以使用新的 API key + try: + from .clients.litellm_client import LiteLLMClient + except ImportError: + from clients.litellm_client import LiteLLMClient + agent.deps.llm_client = LiteLLMClient(agent.config) + + try: + # 执行搜索 + response = await agent.search(request) + finally: + # 恢复原来的 API key + if api_key and 'old_api_key' in locals(): + agent.config.litellm_api_key = old_api_key + try: + from .clients.litellm_client import LiteLLMClient + except ImportError: + from clients.litellm_client import LiteLLMClient + agent.deps.llm_client = LiteLLMClient(agent.config) + + if not response.success: + raise HTTPException(status_code=500, detail=response.message or "搜索失败") + + return response + + except HTTPException: + raise + except Exception as e: + logger.error(f"搜索失败: {e}") + raise HTTPException(status_code=500, detail=f"搜索失败: {str(e)}") + + +@app.get("/config", tags=["配置"]) +async def get_config(): + """获取当前配置信息(隐藏敏感信息)""" + if config is None: + raise HTTPException(status_code=503, detail="服务未初始化") + + return { + "litellm_model": config.litellm_model, + "facebook_api_host": config.facebook_api_host, + "max_results": config.max_results, + "log_level": config.log_level, + "timeout": config.timeout + } + + +# ==================== MCP 协议端点 ==================== + +@app.post("/mcp", tags=["MCP"]) +async def mcp_http_endpoint(request: Request): + """MCP HTTP 端点 - Streamable HTTP""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") + + # 从请求头获取 API key + api_key = request.headers.get("api-key") or request.headers.get("api_key") + if not api_key: + auth_header = request.headers.get("Authorization") + if auth_header: + if auth_header.startswith("Bearer "): + api_key = auth_header[7:] + else: + api_key = auth_header + + response = await handle_mcp_request(body, session_id, api_key=api_key) + + # 如果创建了新会话,返回 session ID + if "result" in response and isinstance(response["result"], dict): + if "sessionId" not in response["result"] and session_id: + response["result"]["sessionId"] = session_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": f"Parse error: {str(e)}" + } + } + ) + + +@app.get("/mcp/sse", tags=["MCP"]) +async def mcp_sse_endpoint(request: Request): + """MCP SSE 端点 - Server-Sent Events""" + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def event_stream() -> AsyncGenerator[str, None]: + # 发送初始连接消息 + yield f"data: {json.dumps({'type': 'connection', 'sessionId': session_id})}\n\n" + + # 发送工具列表 + tools = list(TOOL_MAP.keys()) + yield f"data: {json.dumps({'type': 'tools', 'tools': tools})}\n\n" + + # 保持连接 + import asyncio + while True: + await asyncio.sleep(30) # 心跳 + yield f"data: {json.dumps({'type': 'ping'})}\n\n" + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + +@app.post("/mcp/sse", tags=["MCP"]) +async def mcp_sse_post(request: Request): + """MCP SSE POST 端点 - 处理 SSE 请求""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + # 从请求头获取 API key + api_key = request.headers.get("api-key") or request.headers.get("api_key") + if not api_key: + auth_header = request.headers.get("Authorization") + if auth_header: + if auth_header.startswith("Bearer "): + api_key = auth_header[7:] + else: + api_key = auth_header + + async def response_stream() -> AsyncGenerator[str, None]: + response = await handle_mcp_request(body, session_id, api_key=api_key) + yield f"data: {json.dumps(response)}\n\n" + + return StreamingResponse( + response_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + except Exception as e: + return JSONResponse( + status_code=400, + content={ + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": f"Parse error: {str(e)}" + } + } + ) + + +if __name__ == "__main__": + import uvicorn + + # 运行服务 + uvicorn.run( + "facebook_agent.api:app", + host="0.0.0.0", + port=8000, + reload=True, + log_level="info" + ) + diff --git a/agent_templates/agents/facebook_agent/clients/__init__.py b/agent_templates/agents/facebook_agent/clients/__init__.py new file mode 100644 index 0000000..13df3bc --- /dev/null +++ b/agent_templates/agents/facebook_agent/clients/__init__.py @@ -0,0 +1,10 @@ +""" +客户端模块 +包含各种API客户端的封装 +""" + +from .facebook_client import FacebookClient +from .litellm_client import LiteLLMClient + +__all__ = ["FacebookClient", "LiteLLMClient"] + diff --git a/agent_templates/agents/facebook_agent/clients/facebook_client.py b/agent_templates/agents/facebook_agent/clients/facebook_client.py new file mode 100644 index 0000000..7065e9c --- /dev/null +++ b/agent_templates/agents/facebook_agent/clients/facebook_client.py @@ -0,0 +1,141 @@ +""" +Facebook API客户端 +封装与RapidAPI Facebook接口的交互 +通过 RapidAPI MCP 服务器调用 +""" + +import aiohttp +import json +from typing import List, Optional +from loguru import logger + +# 支持相对导入和绝对导入 +try: + from ..config import Config + from ..models.schemas import SearchResultItem +except ImportError: + from config import Config + from models.schemas import SearchResultItem + + +class FacebookClient: + """Facebook API客户端 - 通过 RapidAPI MCP 服务器调用""" + + def __init__(self, config: Config): + """ + 初始化客户端 + + Args: + config: 配置对象 + """ + self.config = config + # 使用 RapidAPI MCP 服务器 + self.mcp_url = config.facebook_mcp_url + self.headers = { + "x-api-host": config.facebook_api_host, + "x-api-key": config.facebook_api_key, + "Content-Type": "application/json" + } + self.timeout = aiohttp.ClientTimeout(total=config.timeout) + + async def search(self, keyword: str, limit: int = 10) -> List[SearchResultItem]: + """ + 搜索Facebook内容 - 通过 RapidAPI MCP 服务器 + + Args: + keyword: 搜索关键词 + limit: 返回结果数量限制 + + Returns: + 搜索结果列表 + """ + try: + # 通过 RapidAPI MCP 服务器调用 Search_post 工具 + mcp_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "tools/call", + "params": { + "name": "Search_post", + "arguments": { + "query": keyword, + "recent_posts": True # 获取最近的帖子 + } + } + } + + async with aiohttp.ClientSession(timeout=self.timeout) as session: + async with session.post( + self.mcp_url, + headers=self.headers, + json=mcp_request + ) as response: + if response.status != 200: + error_text = await response.text() + logger.error(f"RapidAPI MCP错误: {response.status} - {error_text}") + raise Exception(f"RapidAPI MCP请求失败: {response.status}") + + mcp_response = await response.json() + + # 检查 MCP 响应是否有错误 + if "error" in mcp_response: + error_msg = mcp_response["error"].get("message", "Unknown error") + logger.error(f"RapidAPI MCP工具调用错误: {error_msg}") + raise Exception(f"RapidAPI MCP工具调用失败: {error_msg}") + + # 解析 MCP 响应 + result = mcp_response.get("result", {}) + content = result.get("content", []) + + if not content: + logger.warning(f"搜索关键词 '{keyword}' 未获得结果") + return [] + + # 第一个 content 包含 JSON 字符串 + content_text = content[0].get("text", "{}") + data = json.loads(content_text) + + # 解析搜索结果 + results = [] + items = data.get("results", []) + + for item in items[:limit]: + author_info = item.get("author", {}) + reactions = item.get("reactions", {}) + + results.append(SearchResultItem( + title=item.get("message", "")[:100] or f"Post {item.get('post_id', '')}", + url=item.get("url", ""), + snippet=item.get("message", "")[:500], + author=author_info.get("name", ""), + likes=reactions.get("like", 0) + reactions.get("love", 0), + cover_image=item.get("image", {}).get("uri", "") if item.get("image") else None + )) + + logger.info(f"搜索关键词 '{keyword}' 获得 {len(results)} 条结果") + return results + + except json.JSONDecodeError as e: + logger.error(f"JSON解析错误: {e}") + raise Exception(f"响应解析失败: {str(e)}") + except aiohttp.ClientError as e: + logger.error(f"RapidAPI MCP网络错误: {e}") + raise Exception(f"网络请求失败: {str(e)}") + except Exception as e: + logger.error(f"Facebook搜索异常: {e}") + raise + + async def search_by_mcp(self, keyword: str, limit: int = 10) -> List[SearchResultItem]: + """ + 通过MCP服务器搜索Facebook内容(已弃用,search方法已使用MCP) + + Args: + keyword: 搜索关键词 + limit: 返回结果数量限制 + + Returns: + 搜索结果列表 + """ + # search 方法已经通过 MCP 调用,此方法保留用于兼容性 + return await self.search(keyword, limit) + diff --git a/agent_templates/agents/facebook_agent/clients/litellm_client.py b/agent_templates/agents/facebook_agent/clients/litellm_client.py new file mode 100644 index 0000000..c92d82b --- /dev/null +++ b/agent_templates/agents/facebook_agent/clients/litellm_client.py @@ -0,0 +1,176 @@ +""" +LiteLLM Gateway客户端 +封装与LiteLLM Gateway的交互 +""" + +import aiohttp +from typing import List, Dict, Any, Optional +from loguru import logger + +# 支持相对导入和绝对导入 +try: + from ..config import Config +except ImportError: + from config import Config + + +class LiteLLMClient: + """LiteLLM Gateway客户端""" + + def __init__(self, config: Config): + """ + 初始化客户端 + + Args: + config: 配置对象 + """ + self.config = config + # 确保 base_url 不包含尾部的斜杠,并且正确处理 /v1 路径 + base_url = config.litellm_gateway_url.rstrip("/") + # 如果 base_url 已经包含 /v1,则直接使用;否则添加 /v1 + if base_url.endswith("/v1"): + self.base_url = base_url + else: + self.base_url = f"{base_url}/v1" + self.api_key = config.litellm_api_key + # 移除 openai: 前缀(LiteLLM Gateway 不需要) + model_name = config.litellm_model + if model_name.startswith("openai:"): + model_name = model_name[7:] # 移除 "openai:" 前缀 + self.model = model_name + self.timeout = aiohttp.ClientTimeout(total=config.timeout) + + async def chat( + self, + messages: List[Dict[str, str]], + temperature: float = 0.7, + max_tokens: int = 2000, + response_format: Optional[Dict[str, str]] = None + ) -> str: + """ + 发送聊天请求到LiteLLM Gateway + + Args: + messages: 消息列表,格式 [{"role": "user", "content": "..."}] + temperature: 温度参数 + max_tokens: 最大token数 + response_format: 响应格式(如 {"type": "json_object"}) + + Returns: + LLM的响应文本 + """ + # LiteLLM Gateway通常兼容OpenAI API格式 + # base_url 已经包含 /v1,所以直接添加 /chat/completions + url = f"{self.base_url}/chat/completions" + + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + payload = { + "model": self.model, + "messages": messages, + "temperature": temperature, + "max_tokens": max_tokens + } + + if response_format: + payload["response_format"] = response_format + + try: + async with aiohttp.ClientSession(timeout=self.timeout) as session: + async with session.post(url, headers=headers, json=payload) as response: + if response.status != 200: + error_text = await response.text() + logger.error(f"LiteLLM API错误: {response.status} - {error_text}") + raise Exception(f"LiteLLM API请求失败: {response.status}") + + result = await response.json() + return result["choices"][0]["message"]["content"] + + except aiohttp.ClientError as e: + logger.error(f"LiteLLM请求网络错误: {e}") + raise + except Exception as e: + logger.error(f"LiteLLM请求异常: {e}") + raise + + async def chat_with_system( + self, + system_prompt: str, + user_message: str, + temperature: float = 0.7, + max_tokens: int = 2000, + response_format: Optional[Dict[str, str]] = None + ) -> str: + """ + 使用系统提示和用户消息进行对话 + + Args: + system_prompt: 系统提示 + user_message: 用户消息 + temperature: 温度参数 + max_tokens: 最大token数 + response_format: 响应格式 + + Returns: + LLM的响应文本 + """ + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_message} + ] + + return await self.chat( + messages=messages, + temperature=temperature, + max_tokens=max_tokens, + response_format=response_format + ) + + async def generate_summary( + self, + query: str, + results: List[Dict[str, Any]], + temperature: float = 0.7 + ) -> str: + """ + 生成搜索结果总结 + + Args: + query: 搜索关键词 + results: 搜索结果列表 + temperature: 温度参数 + + Returns: + AI生成的总结文本 + """ + system_prompt = """你是一个Facebook内容分析助手。用户给你搜索关键词和搜索结果,你需要: +1. 总结这些内容的主要特点和亮点 +2. 提取关键信息(如热门话题、用户关注点等) +3. 用简洁、友好的语言呈现 +4. 如果结果较少,说明可能的原因或建议""" + + results_text = "\n\n".join([ + f"标题: {r.get('title', '')}\n" + f"摘要: {r.get('snippet', '')}\n" + f"作者: {r.get('author', '')}\n" + f"点赞: {r.get('likes', 0)}" + for r in results + ]) + + user_message = f"""用户搜索关键词:{query} + +搜索结果: +{results_text} + +请为这些搜索结果生成一个简洁的总结。""" + + return await self.chat_with_system( + system_prompt=system_prompt, + user_message=user_message, + temperature=temperature, + max_tokens=1000 + ) + diff --git a/agent_templates/agents/facebook_agent/config.py b/agent_templates/agents/facebook_agent/config.py new file mode 100644 index 0000000..2559d4b --- /dev/null +++ b/agent_templates/agents/facebook_agent/config.py @@ -0,0 +1,89 @@ +""" +配置管理模块 +负责加载和管理所有配置项 +""" + +import os +from dataclasses import dataclass +from typing import Optional +from dotenv import load_dotenv + + +@dataclass +class Config: + """Agent配置类""" + + # LiteLLM Gateway配置 + litellm_gateway_url: str + litellm_api_key: str + litellm_model: str = "taiji/gpt-4o-mini" + + # Facebook RapidAPI配置 + facebook_api_host: str = "facebook-scraper3.p.rapidapi.com" + facebook_api_key: str = "34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00" + facebook_mcp_url: str = "https://mcp.rapidapi.com" + + # Agent配置 + max_results: int = 10 + timeout: int = 30 + + # 可选配置 + log_level: str = "INFO" + + @staticmethod + def ensure_model_prefix(model_name: str) -> str: + """确保模型名称有 openai: 前缀(pydantic_ai 要求格式为 provider:model_name)""" + if not model_name: + return 'openai:taiji/gpt-4o-mini' + # 如果已经有 provider: 前缀,直接返回 + if ':' in model_name: + return model_name + # 否则添加 openai: 前缀 + return f'openai:{model_name}' + + @classmethod + def from_env(cls, env_path: Optional[str] = None) -> "Config": + """从环境变量加载配置""" + if env_path: + load_dotenv(env_path) + else: + load_dotenv() + + # 获取模型名称并确保有 openai: 前缀 + raw_model = os.getenv("LITELLM_MODEL", "taiji/gpt-4o-mini") + model_name = cls.ensure_model_prefix(raw_model) + + return cls( + # LiteLLM Gateway配置 + litellm_gateway_url=os.getenv("LITELLM_GATEWAY_URL", ""), + litellm_api_key=os.getenv("LITELLM_API_KEY", "sk"), + litellm_model=model_name, + + # Facebook RapidAPI配置 + facebook_api_host=os.getenv("FACEBOOK_API_HOST", "facebook-scraper3.p.rapidapi.com"), + facebook_api_key=os.getenv("FACEBOOK_API_KEY", "34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00"), + facebook_mcp_url=os.getenv("FACEBOOK_MCP_URL", "https://mcp.rapidapi.com"), + + # Agent配置 + max_results=int(os.getenv("MAX_RESULTS", "10")), + timeout=int(os.getenv("TIMEOUT", "30")), + + # 可选配置 + log_level=os.getenv("LOG_LEVEL", "INFO") + ) + + def validate(self) -> bool: + """验证配置是否完整""" + required_fields = [ + ("litellm_gateway_url", self.litellm_gateway_url), + ("litellm_api_key", self.litellm_api_key), + ("facebook_api_key", self.facebook_api_key), + ] + + missing = [name for name, value in required_fields if not value] + + if missing: + raise ValueError(f"缺少必要的配置项: {', '.join(missing)}") + + return True + diff --git a/agent_templates/agents/facebook_agent/cursor-mcp-config.json b/agent_templates/agents/facebook_agent/cursor-mcp-config.json new file mode 100644 index 0000000..9903ce3 --- /dev/null +++ b/agent_templates/agents/facebook_agent/cursor-mcp-config.json @@ -0,0 +1,28 @@ +{ + "mcpServers": { + "facebook-search-agent-stdio": { + "command": "python", + "args": ["/home/taiji/tools/aks_agent/facebook_agent/run_mcp_server.py", "--transport", "stdio"], + "env": { + "LITELLM_GATEWAY_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1", + "LITELLM_API_KEY": "sk-rxegkFOciNmQLhOHr3qP3A", + "LITELLM_MODEL": "taiji/gpt-4o-mini", + "FACEBOOK_API_HOST": "facebook-scraper3.p.rapidapi.com", + "FACEBOOK_API_KEY": "34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00", + "FACEBOOK_MCP_URL": "https://mcp.rapidapi.com", + "MAX_RESULTS": "10", + "TIMEOUT": "30", + "LOG_LEVEL": "INFO" + } + }, + "facebook-search-agent-http": { + "url": "http://20.6.9.191:18000/mcp", + "type": "http" + }, + "facebook-search-agent-sse": { + "url": "http://20.6.9.191:18000/mcp/sse", + "type": "sse" + } + } +} + diff --git a/agent_templates/agents/facebook_agent/docker-compose.yml b/agent_templates/agents/facebook_agent/docker-compose.yml new file mode 100644 index 0000000..0695524 --- /dev/null +++ b/agent_templates/agents/facebook_agent/docker-compose.yml @@ -0,0 +1,31 @@ +services: + facebook-agent: + build: + context: .. + dockerfile: facebook_agent/Dockerfile.mcp + container_name: facebook-agent + ports: + - "18000:8000" # 统一端口:API + MCP 服务 + environment: + - LITELLM_GATEWAY_URL=${LITELLM_GATEWAY_URL:-https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1} + - LITELLM_API_KEY=${LITELLM_API_KEY:-sk-rxegkFOciNmQLhOHr3qP3A} + - LITELLM_MODEL=${LITELLM_MODEL:-taiji/gpt-4o-mini} + - FACEBOOK_API_HOST=${FACEBOOK_API_HOST:-facebook-scraper3.p.rapidapi.com} + - FACEBOOK_API_KEY=${FACEBOOK_API_KEY:-34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00} + - FACEBOOK_MCP_URL=${FACEBOOK_MCP_URL:-https://mcp.rapidapi.com} + - MAX_RESULTS=${MAX_RESULTS:-10} + - TIMEOUT=${TIMEOUT:-30} + - LOG_LEVEL=${LOG_LEVEL:-INFO} + - API_HOST=0.0.0.0 + - API_PORT=8000 + - PYTHONPATH=/app + # volumes: + # - ./facebook_agent:/app/facebook_agent + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + diff --git a/agent_templates/agents/facebook_agent/docker-test.sh b/agent_templates/agents/facebook_agent/docker-test.sh new file mode 100755 index 0000000..37e98ce --- /dev/null +++ b/agent_templates/agents/facebook_agent/docker-test.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Docker测试脚本 + +set -e + +echo "==========================================" +echo "Facebook搜索Agent Docker测试" +echo "==========================================" + +# 颜色定义 +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 检查Docker是否安装 +if ! command -v docker &> /dev/null; then + echo -e "${RED}错误: Docker未安装${NC}" + exit 1 +fi + +# 检查docker-compose是否安装 +if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then + echo -e "${RED}错误: docker-compose未安装${NC}" + exit 1 +fi + +# 使用docker compose或docker-compose +if docker compose version &> /dev/null; then + DOCKER_COMPOSE="docker compose" +else + DOCKER_COMPOSE="docker-compose" +fi + +echo -e "${YELLOW}1. 构建Docker镜像...${NC}" +cd /home/taiji/tools/aks_agent/facebook_agent +$DOCKER_COMPOSE build + +echo -e "${YELLOW}2. 启动服务...${NC}" +$DOCKER_COMPOSE up -d + +echo -e "${YELLOW}3. 等待服务启动...${NC}" +sleep 5 + +echo -e "${YELLOW}4. 检查服务状态...${NC}" +$DOCKER_COMPOSE ps + +echo -e "${YELLOW}5. 测试API服务 (端口18000)...${NC}" +echo "测试根路径:" +curl -s http://localhost:18000/ | python3 -m json.tool || echo -e "${RED}API服务未响应${NC}" + +echo -e "\n测试健康检查:" +curl -s http://localhost:18000/health | python3 -m json.tool || echo -e "${RED}健康检查失败${NC}" + +echo -e "\n${YELLOW}6. 测试搜索功能...${NC}" +SEARCH_RESULT=$(curl -s -X POST http://localhost:18000/search \ + -H "Content-Type: application/json" \ + -d '{"query": "technology news", "limit": 3}') + +if [ $? -eq 0 ]; then + echo "$SEARCH_RESULT" | python3 -m json.tool | head -50 + echo -e "${GREEN}搜索测试完成${NC}" +else + echo -e "${RED}搜索测试失败${NC}" +fi + +echo -e "\n${YELLOW}7. 测试MCP服务 (端口18001)...${NC}" +echo "测试MCP健康检查:" +curl -s http://localhost:18001/health | python3 -m json.tool || echo -e "${RED}MCP服务未响应${NC}" + +echo -e "\n测试MCP工具列表:" +curl -s -X POST http://localhost:18001/mcp/call \ + -H "Content-Type: application/json" \ + -d '{"method": "tools/list", "params": {"id": "test"}}' | python3 -m json.tool || echo -e "${RED}MCP工具列表获取失败${NC}" + +echo -e "\n${YELLOW}8. 查看服务日志...${NC}" +echo "API服务日志 (最后10行):" +docker logs facebook-api --tail 10 2>&1 || echo "无法获取日志" + +echo -e "\nMCP服务日志 (最后10行):" +docker logs facebook-mcp --tail 10 2>&1 || echo "无法获取日志" + +echo -e "\n${GREEN}==========================================" +echo "测试完成!" +echo "==========================================${NC}" +echo "" +echo "服务地址:" +echo " - API服务: http://20.6.9.191:18000" +echo " - MCP服务: http://20.6.9.191:18001" +echo "" +echo "查看日志: docker logs facebook-api 或 docker logs facebook-mcp" +echo "停止服务: docker-compose down" + diff --git a/agent_templates/agents/facebook_agent/k8s_deployment.yaml b/agent_templates/agents/facebook_agent/k8s_deployment.yaml new file mode 100644 index 0000000..dc2b931 --- /dev/null +++ b/agent_templates/agents/facebook_agent/k8s_deployment.yaml @@ -0,0 +1,131 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: facebook-agent +--- +apiVersion: v1 +kind: Secret +metadata: + name: facebook-agent-secrets + namespace: facebook-agent +type: Opaque +stringData: + LITELLM_API_KEY: "sk-rxegkFOciNmQLhOHr3qP3A" + FACEBOOK_API_KEY: "34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: facebook-agent-config + namespace: facebook-agent +data: + LITELLM_MODEL: "taiji/gpt-4o-mini" + FACEBOOK_API_HOST: "facebook-scraper3.p.rapidapi.com" + FACEBOOK_MCP_URL: "https://mcp.rapidapi.com" + MAX_RESULTS: "10" + TIMEOUT: "30" + LOG_LEVEL: "INFO" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: facebook-agent + namespace: facebook-agent + labels: + app: facebook-agent +spec: + replicas: 2 + selector: + matchLabels: + app: facebook-agent + template: + metadata: + labels: + app: facebook-agent + spec: + containers: + - name: agent + image: your-registry.azurecr.io/facebook-agent:latest + ports: + - containerPort: 8000 + name: http + env: + - name: LITELLM_GATEWAY_URL + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: LITELLM_GATEWAY_URL + - name: LITELLM_API_KEY + valueFrom: + secretKeyRef: + name: facebook-agent-secrets + key: LITELLM_API_KEY + - name: LITELLM_MODEL + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: LITELLM_MODEL + - name: FACEBOOK_API_HOST + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: FACEBOOK_API_HOST + - name: FACEBOOK_API_KEY + valueFrom: + secretKeyRef: + name: facebook-agent-secrets + key: FACEBOOK_API_KEY + - name: FACEBOOK_MCP_URL + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: FACEBOOK_MCP_URL + - name: MAX_RESULTS + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: MAX_RESULTS + - name: TIMEOUT + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: TIMEOUT + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + name: facebook-agent-config + key: LOG_LEVEL + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: facebook-agent-service + namespace: facebook-agent +spec: + selector: + app: facebook-agent + ports: + - protocol: TCP + port: 80 + targetPort: 8000 + type: LoadBalancer + diff --git a/agent_templates/agents/facebook_agent/main.py b/agent_templates/agents/facebook_agent/main.py new file mode 100644 index 0000000..27b4fb2 --- /dev/null +++ b/agent_templates/agents/facebook_agent/main.py @@ -0,0 +1,121 @@ +""" +主程序入口 +支持命令行和API服务两种模式 +""" + +import asyncio +import sys +from loguru import logger + +from .config import Config +from .agent import FacebookAgent +from .models.schemas import SearchRequest + + +async def main(): + """主函数""" + # 加载配置 + try: + config = Config.from_env() + config.validate() + except Exception as e: + logger.error(f"配置加载失败: {e}") + sys.exit(1) + + # 创建Agent + agent = FacebookAgent(config) + + # 检查命令行参数 + if len(sys.argv) > 1: + # 命令行模式 + query = sys.argv[1] + limit = int(sys.argv[2]) if len(sys.argv) > 2 else 5 + + logger.info(f"搜索关键词: {query}") + + request = SearchRequest(query=query, limit=limit) + response = await agent.search(request) + + if response.success: + print("\n" + "=" * 60) + print("搜索结果") + print("=" * 60) + print(f"关键词: {response.query}") + print(f"找到 {len(response.results)} 条结果\n") + + for i, result in enumerate(response.results, 1): + print(f"[{i}] {result.title}") + if result.snippet: + print(f" {result.snippet[:100]}...") + if result.author: + print(f" 作者: {result.author}") + if result.likes: + print(f" 点赞: {result.likes}") + if result.url: + print(f" 链接: {result.url}") + print() + + if response.summary: + print("=" * 60) + print("AI总结") + print("=" * 60) + print(response.summary) + print() + else: + print(f"搜索失败: {response.message}") + sys.exit(1) + else: + # 交互模式 + print("=" * 60) + print("Facebook搜索智能Agent") + print("=" * 60) + print("输入搜索关键词,输入 'quit' 或 'exit' 退出") + print("=" * 60) + + while True: + try: + query = input("\n🔎 请输入关键词: ").strip() + + if query.lower() in ['quit', 'exit', '退出']: + print("再见!") + break + + if not query: + continue + + request = SearchRequest(query=query, limit=5) + response = await agent.search(request) + + if response.success: + print("\n" + "=" * 60) + print(f"找到 {len(response.results)} 条结果") + print("=" * 60) + + for i, result in enumerate(response.results, 1): + print(f"\n[{i}] {result.title}") + if result.snippet: + print(f" {result.snippet[:150]}...") + if result.author: + print(f" 👤 {result.author}") + if result.likes: + print(f" ❤️ {result.likes}") + + if response.summary: + print("\n" + "=" * 60) + print("📝 AI总结") + print("=" * 60) + print(response.summary) + else: + print(f"❌ 搜索失败: {response.message}") + + except KeyboardInterrupt: + print("\n\n再见!") + break + except Exception as e: + logger.error(f"处理失败: {e}") + print(f"❌ 发生错误: {e}") + + +if __name__ == "__main__": + asyncio.run(main()) + diff --git a/agent_templates/agents/facebook_agent/mcp_config.json b/agent_templates/agents/facebook_agent/mcp_config.json new file mode 100644 index 0000000..9839288 --- /dev/null +++ b/agent_templates/agents/facebook_agent/mcp_config.json @@ -0,0 +1,22 @@ +{ + "mcpServers": { + "Facebook Search Agent": { + "command": "python", + "args": [ + "-m", + "facebook_agent.mcp_server" + ], + "env": { + "LITELLM_GATEWAY_URL": "https://your-litellm-gateway-url", + "LITELLM_API_KEY": "sk-", + "LITELLM_MODEL": "taiji/gpt-4o-mini", + "FACEBOOK_API_HOST": "facebook-scraper3.p.rapidapi.com", + "FACEBOOK_API_KEY": "34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00", + "FACEBOOK_MCP_URL": "https://mcp.rapidapi.com", + "MAX_RESULTS": "10", + "TIMEOUT": "30", + "LOG_LEVEL": "INFO" + } + } + } +} diff --git a/agent_templates/agents/facebook_agent/mcp_config_remote.json b/agent_templates/agents/facebook_agent/mcp_config_remote.json new file mode 100644 index 0000000..dde6781 --- /dev/null +++ b/agent_templates/agents/facebook_agent/mcp_config_remote.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "RapidAPI Hub - Facebook Scraper": { + "command": "npx", + "args": [ + "mcp-remote", + "https://mcp.rapidapi.com", + "--header", + "x-api-host: facebook-scraper3.p.rapidapi.com", + "--header", + "x-api-key: 34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00" + ] + } + } +} diff --git a/agent_templates/agents/facebook_agent/mcp_http_server.py b/agent_templates/agents/facebook_agent/mcp_http_server.py new file mode 100644 index 0000000..2827c19 --- /dev/null +++ b/agent_templates/agents/facebook_agent/mcp_http_server.py @@ -0,0 +1,327 @@ +""" +MCP HTTP/SSE 服务器 - 支持远程调用 +实现 MCP 协议的 HTTP 和 SSE 传输方式,供 Cursor 等客户端远程调用 +""" +import json +import os +import uuid +from typing import Dict, Any, Optional, AsyncGenerator +from fastapi import FastAPI, Request, HTTPException, Header +from fastapi.responses import StreamingResponse, JSONResponse +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from loguru import logger + +# 导入 MCP 服务器和工具函数 +from .mcp_server import ( + server as mcp_server, + search_facebook, + initialize_agent +) + +# 工具映射 +TOOL_MAP = { + 'search_facebook': search_facebook, +} + + +# 创建 FastAPI 应用 +app = FastAPI( + title="MCP HTTP/SSE Server - Facebook搜索Agent", + description="MCP 协议的 HTTP 和 SSE 传输实现", + version="1.0.0" +) + +# 配置 CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Session 管理 +sessions: Dict[str, Dict[str, Any]] = {} + + +class MCPRequest(BaseModel): + """MCP JSON-RPC 请求""" + jsonrpc: str = "2.0" + id: Optional[str] = None + method: str + params: Optional[Dict[str, Any]] = None + + +class MCPResponse(BaseModel): + """MCP JSON-RPC 响应""" + jsonrpc: str = "2.0" + id: Optional[str] = None + result: Optional[Any] = None + error: Optional[Dict[str, Any]] = None + + +async def handle_mcp_request(request_data: Dict[str, Any], session_id: Optional[str] = None) -> Dict[str, Any]: + """处理 MCP 请求""" + method = request_data.get("method") + params = request_data.get("params", {}) + request_id = request_data.get("id") + + try: + if method == "initialize": + # 初始化会话 + if not session_id: + session_id = str(uuid.uuid4()) + sessions[session_id] = { + "initialized": True, + "capabilities": {} + } + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {}, + "resources": {} + }, + "serverInfo": { + "name": "Facebook搜索Agent", + "version": "1.0.0" + } + } + } + + elif method == "tools/list": + # 列出所有工具 + tools = [ + { + "name": "search_facebook", + "description": "搜索Facebook内容,返回相关帖子和AI生成的总结。支持搜索关键词,返回帖子标题、链接、摘要、作者、点赞数等信息,并提供AI生成的总结。", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "搜索关键词,例如:technology news、travel tips、food recipes等" + }, + "limit": { + "type": "integer", + "description": "返回结果数量,默认5,最大20", + "default": 5, + "minimum": 1, + "maximum": 20 + } + }, + "required": ["query"] + } + } + ] + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": tools + } + } + + elif method == "tools/call": + # 调用工具 + tool_name = params.get("name") + arguments = params.get("arguments", {}) + + if tool_name not in TOOL_MAP: + raise ValueError(f"Tool '{tool_name}' not found") + + # 获取工具函数 + tool_func = TOOL_MAP[tool_name] + + # 调用工具(异步) + result = await tool_func(**arguments) + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + { + "type": "text", + "text": str(result) + } + ] + } + } + + elif method == "ping": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {} + } + + else: + raise ValueError(f"Unknown method: {method}") + + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": str(e) + } + } + + +@app.on_event("startup") +async def startup_event(): + """应用启动时初始化""" + try: + # 初始化Agent + initialize_agent() + + logger.info("=" * 60) + logger.info("Facebook搜索Agent MCP HTTP服务器启动") + logger.info("=" * 60) + logger.info("MCP服务器已就绪,支持HTTP/SSE传输") + + except Exception as e: + logger.error(f"启动失败: {e}") + raise + + +@app.post("/mcp") +async def mcp_http_endpoint(request: Request): + """MCP HTTP 端点 - Streamable HTTP""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") + + response = await handle_mcp_request(body, session_id) + + # 如果创建了新会话,返回 session ID + if "result" in response and isinstance(response["result"], dict): + if "sessionId" not in response["result"] and session_id: + response["result"]["sessionId"] = session_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": f"Parse error: {str(e)}" + } + } + ) + + +@app.get("/mcp/sse") +async def mcp_sse_endpoint(request: Request): + """MCP SSE 端点 - Server-Sent Events""" + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def event_stream() -> AsyncGenerator[str, None]: + # 发送初始连接消息 + yield f"data: {json.dumps({'type': 'connection', 'sessionId': session_id})}\n\n" + + # 发送工具列表 + tools = list(TOOL_MAP.keys()) + yield f"data: {json.dumps({'type': 'tools', 'tools': tools})}\n\n" + + # 保持连接 + import asyncio + while True: + await asyncio.sleep(30) # 心跳 + yield f"data: {json.dumps({'type': 'ping'})}\n\n" + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + +@app.post("/mcp/sse") +async def mcp_sse_post(request: Request): + """MCP SSE POST 端点 - 处理 SSE 请求""" + try: + body = await request.json() + session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4()) + + async def response_stream() -> AsyncGenerator[str, None]: + response = await handle_mcp_request(body, session_id) + yield f"data: {json.dumps(response)}\n\n" + + return StreamingResponse( + response_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "x-mcp-session-id": session_id + } + ) + + except Exception as e: + return JSONResponse( + status_code=400, + content={ + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": f"Parse error: {str(e)}" + } + } + ) + + +@app.get("/health") +async def health_check(): + """健康检查""" + return { + "status": "healthy", + "service": "MCP HTTP/SSE Server - Facebook搜索Agent", + "tools_count": len(TOOL_MAP) + } + + +@app.get("/") +async def root(): + """根端点""" + return { + "service": "MCP HTTP/SSE Server - Facebook搜索Agent", + "version": "1.0.0", + "endpoints": { + "mcp_http": "/mcp", + "mcp_sse": "/mcp/sse", + "health": "/health" + }, + "tools": list(TOOL_MAP.keys()) + } + + +if __name__ == "__main__": + import uvicorn + host = os.getenv("MCP_HOST", "0.0.0.0") + port = int(os.getenv("MCP_PORT", "8001")) + + print(f"🚀 MCP HTTP/SSE Server 启动中...") + print(f"📡 HTTP 端点: http://{host}:{port}/mcp") + print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse") + print(f"📚 健康检查: http://{host}:{port}/health") + + uvicorn.run(app, host=host, port=port) + diff --git a/agent_templates/agents/facebook_agent/mcp_server.py b/agent_templates/agents/facebook_agent/mcp_server.py new file mode 100644 index 0000000..1a50d69 --- /dev/null +++ b/agent_templates/agents/facebook_agent/mcp_server.py @@ -0,0 +1,123 @@ +""" +MCP 服务器 - Facebook 搜索 Agent +使用 FastMCP 提供 Facebook 搜索功能 +""" +import json +import os +from typing import Optional +from loguru import logger + +from mcp.server.fastmcp import FastMCP + +# 支持相对导入和绝对导入 +try: + from .config import Config + from .agent import FacebookAgent + from .models.schemas import SearchRequest +except ImportError: + from config import Config + from agent import FacebookAgent + from models.schemas import SearchRequest + +# 创建 MCP 服务器 +server = FastMCP('Facebook搜索Agent') + +# 全局变量 +agent: Optional[FacebookAgent] = None + + +def initialize_agent(): + """初始化 Agent""" + global agent + if agent is None: + try: + # 加载配置 + config = Config.from_env() + config.validate() + + # 创建Agent + agent = FacebookAgent(config) + + logger.info("=" * 60) + logger.info("Facebook搜索Agent初始化完成") + logger.info("=" * 60) + logger.info(f"LiteLLM Gateway: {config.litellm_gateway_url}") + logger.info(f"模型: {config.litellm_model}") + logger.info(f"Facebook API Host: {config.facebook_api_host}") + + except Exception as e: + logger.error(f"Agent初始化失败: {e}") + raise + + +@server.tool() +async def search_facebook( + query: str, + limit: int = 5 +) -> str: + """ + 搜索Facebook内容,返回相关帖子和AI生成的总结 + + Args: + query: 搜索关键词,例如:technology news、travel tips、food recipes等 + limit: 返回结果数量,默认5,最大20 + + Returns: + 搜索结果和AI生成的总结(JSON格式) + """ + global agent + + # 确保Agent已初始化 + if agent is None: + initialize_agent() + + try: + if not query: + return json.dumps({ + "success": False, + "error": "搜索关键词不能为空" + }, ensure_ascii=False) + + # 限制结果数量 + limit = max(1, min(limit, 20)) + + # 执行搜索 + request = SearchRequest(query=query, limit=limit) + response = await agent.search(request) + + # 构造返回结果 + result = { + "success": response.success, + "query": response.query, + "total_count": response.total_count, + "results": [ + { + "title": r.title, + "url": r.url, + "snippet": r.snippet, + "author": r.author, + "likes": r.likes + } + for r in response.results + ], + "summary": response.summary, + "message": response.message + } + + return json.dumps(result, ensure_ascii=False, indent=2) + + except Exception as e: + logger.error(f"搜索处理失败: {e}") + return json.dumps({ + "success": False, + "error": str(e) + }, ensure_ascii=False) + + +if __name__ == '__main__': + # 初始化Agent + initialize_agent() + + # 运行服务器 + server.run() + diff --git a/agent_templates/agents/facebook_agent/models/__init__.py b/agent_templates/agents/facebook_agent/models/__init__.py new file mode 100644 index 0000000..c3532a7 --- /dev/null +++ b/agent_templates/agents/facebook_agent/models/__init__.py @@ -0,0 +1,9 @@ +""" +数据模型模块 +包含所有Pydantic数据模型定义 +""" + +from .schemas import SearchRequest, SearchResponse, SearchResultItem + +__all__ = ["SearchRequest", "SearchResponse", "SearchResultItem"] + diff --git a/agent_templates/agents/facebook_agent/models/schemas.py b/agent_templates/agents/facebook_agent/models/schemas.py new file mode 100644 index 0000000..fd918e4 --- /dev/null +++ b/agent_templates/agents/facebook_agent/models/schemas.py @@ -0,0 +1,36 @@ +""" +数据模型定义 +使用Pydantic定义输入输出结构 +""" + +from pydantic import BaseModel, Field +from typing import List, Optional +from datetime import datetime + + +class SearchRequest(BaseModel): + """搜索请求模型""" + query: str = Field(..., description="搜索关键词", min_length=1, max_length=200) + limit: Optional[int] = Field(5, description="返回结果数量", ge=1, le=20) + + +class SearchResultItem(BaseModel): + """Facebook搜索结果项""" + title: str = Field(..., description="笔记标题") + url: Optional[str] = Field(None, description="笔记链接") + snippet: Optional[str] = Field(None, description="笔记摘要") + author: Optional[str] = Field(None, description="作者") + likes: Optional[int] = Field(None, description="点赞数") + cover_image: Optional[str] = Field(None, description="封面图片URL") + + +class SearchResponse(BaseModel): + """搜索响应模型""" + success: bool = Field(..., description="是否成功") + query: str = Field(..., description="搜索关键词") + results: List[SearchResultItem] = Field(default_factory=list, description="搜索结果列表") + total_count: Optional[int] = Field(None, description="总结果数") + summary: Optional[str] = Field(None, description="AI生成的总结") + message: Optional[str] = Field(None, description="错误消息或提示信息") + timestamp: str = Field(default_factory=lambda: datetime.now().isoformat(), description="时间戳") + diff --git a/agent_templates/agents/facebook_agent/requirements.txt b/agent_templates/agents/facebook_agent/requirements.txt new file mode 100644 index 0000000..4cf82bf --- /dev/null +++ b/agent_templates/agents/facebook_agent/requirements.txt @@ -0,0 +1,28 @@ +# Pydantic AI框架 +pydantic-ai>=0.0.14 + +# HTTP客户端 +aiohttp>=3.9.0 +httpx>=0.25.0 + +# FastAPI相关 +fastapi>=0.109.0 +uvicorn[standard]>=0.27.0 +pydantic>=2.5.0 + +# MCP支持 +mcp>=0.9.0 +fastmcp>=0.1.0 +# 注意:如果mcp包不可用,可以使用以下替代方案: +# - 使用FastAPI直接实现MCP协议 +# - 或等待官方MCP Python SDK更新 + +# 环境变量 +python-dotenv>=1.0.0 + +# 日志 +loguru>=0.7.0 + +# 类型提示 +typing-extensions>=4.9.0 + diff --git a/agent_templates/agents/facebook_agent/run_api.py b/agent_templates/agents/facebook_agent/run_api.py new file mode 100644 index 0000000..b716c69 --- /dev/null +++ b/agent_templates/agents/facebook_agent/run_api.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +""" +启动 Facebook Agent API 服务的脚本 +解决相对导入问题 +""" +import sys +import os +from pathlib import Path + +# 将当前目录添加到 Python 路径 +current_dir = Path(__file__).parent +sys.path.insert(0, str(current_dir)) + +# 切换到当前目录,使绝对导入能够工作 +os.chdir(current_dir) + +# 直接导入 api 模块 +from api import app +import uvicorn + +if __name__ == "__main__": + host = os.getenv("API_HOST", "0.0.0.0") + port = int(os.getenv("API_PORT", "8000")) + + print(f"🚀 启动 Facebook 搜索智能 Agent API") + print(f"📡 监听地址: http://{host}:{port}") + print(f"📚 API 文档: http://{host}:{port}/docs") + + uvicorn.run(app, host=host, port=port, log_level="info") diff --git a/agent_templates/agents/facebook_agent/run_mcp_http_server.py b/agent_templates/agents/facebook_agent/run_mcp_http_server.py new file mode 100644 index 0000000..100d124 --- /dev/null +++ b/agent_templates/agents/facebook_agent/run_mcp_http_server.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +""" +启动 MCP HTTP/SSE 服务器的入口脚本 +支持远程调用,供 Cursor 等客户端使用 +""" +import sys +import os +from pathlib import Path + +# 添加项目根目录到 Python 路径 +# 脚本在 facebook_agent/ 目录下,需要将父目录添加到路径 +# 在 Docker 容器中,PYTHONPATH 应该设置为 /app +project_root = Path(__file__).parent.parent +if str(project_root) not in sys.path: + sys.path.insert(0, str(project_root)) + +# 确保 PYTHONPATH 包含 /app(Docker 容器中的根目录) +app_root = Path("/app") +if app_root.exists() and str(app_root) not in sys.path: + sys.path.insert(0, str(app_root)) + +if __name__ == '__main__': + # 尝试不同的导入方式 + try: + from facebook_agent.mcp_http_server import app + except ImportError: + # 如果在容器内且工作目录是 /app/facebook_agent,尝试相对导入 + try: + from mcp_http_server import app + except ImportError: + # 最后尝试直接导入 + import mcp_http_server + app = mcp_http_server.app + + import uvicorn + + host = os.getenv("MCP_HOST", "0.0.0.0") + port = int(os.getenv("MCP_PORT", "8001")) + + print(f"🚀 MCP HTTP/SSE Server 启动中...") + print(f"📡 HTTP 端点: http://{host}:{port}/mcp") + print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse") + print(f"📚 健康检查: http://{host}:{port}/health") + print(f"📋 工具列表: http://{host}:{port}/") + print() + print("💡 Cursor 配置示例:") + print(f' "url": "http://{host}:{port}/mcp"') + print(f' "type": "http"') + + uvicorn.run(app, host=host, port=port) + diff --git a/agent_templates/agents/facebook_agent/run_mcp_server.py b/agent_templates/agents/facebook_agent/run_mcp_server.py new file mode 100644 index 0000000..8627a50 --- /dev/null +++ b/agent_templates/agents/facebook_agent/run_mcp_server.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +""" +启动 MCP 服务器的入口脚本 +支持 stdio(本地)和 HTTP/SSE(远程)两种传输方式 +""" +import sys +import os +import argparse +from pathlib import Path + +# 添加项目根目录到 Python 路径 +# 脚本在 facebook_agent/ 目录下,需要将父目录添加到路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='启动 MCP 服务器') + parser.add_argument( + '--transport', + choices=['stdio', 'http', 'sse'], + default='stdio', + help='传输方式: stdio (本地), http (HTTP), sse (SSE)' + ) + parser.add_argument('--host', default='0.0.0.0', help='HTTP/SSE 服务器地址') + parser.add_argument('--port', type=int, default=8001, help='HTTP/SSE 服务器端口') + + args = parser.parse_args() + + if args.transport == 'stdio': + # stdio 模式(本地) + from facebook_agent.mcp_server import server + print("🚀 MCP Server (stdio) 启动中...") + server.run() + else: + # HTTP/SSE 模式(远程) + from facebook_agent.mcp_http_server import app + import uvicorn + + host = args.host + port = args.port + + print(f"🚀 MCP HTTP/SSE Server 启动中...") + print(f"📡 HTTP 端点: http://{host}:{port}/mcp") + print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse") + print(f"📚 健康检查: http://{host}:{port}/health") + print() + print("💡 Cursor 配置示例:") + print(f' "url": "http://{host}:{port}/mcp"') + print(f' "type": "{args.transport}"') + + uvicorn.run(app, host=host, port=port) + diff --git a/agent_templates/agents/facebook_agent/start_api.sh b/agent_templates/agents/facebook_agent/start_api.sh new file mode 100755 index 0000000..0ca67fa --- /dev/null +++ b/agent_templates/agents/facebook_agent/start_api.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Facebook搜索智能Agent API启动脚本 + +set -e + +echo "==========================================" +echo "启动Facebook搜索智能Agent API服务" +echo "==========================================" + +# 检查Python环境 +if ! command -v python3 &> /dev/null; then + echo "错误: 未找到Python3" + exit 1 +fi + +# 检查依赖 +if [ ! -f "requirements.txt" ]; then + echo "错误: 未找到requirements.txt" + exit 1 +fi + +# 安装依赖(如果需要) +if [ "$1" == "--install-deps" ]; then + echo "安装依赖..." + pip install -r requirements.txt +fi + +# 检查.env文件 +if [ ! -f ".env" ]; then + echo "警告: 未找到.env文件,将使用默认配置" + echo "请确保设置了必要的环境变量" +fi + +# 启动服务 +echo "启动API服务..." +python -m uvicorn facebook_agent.api:app --host 0.0.0.0 --port 8000 --reload + diff --git a/agent_templates/agents/facebook_agent/start_both.sh b/agent_templates/agents/facebook_agent/start_both.sh new file mode 100755 index 0000000..fd0923b --- /dev/null +++ b/agent_templates/agents/facebook_agent/start_both.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# 同时启动 API 和 MCP 服务的脚本 +# 使用 supervisor 或简单的后台进程方式 + +set -e + +echo "==========================================" +echo "启动 Facebook Agent (API + MCP)" +echo "==========================================" + +# 检查 Python 环境 +if ! command -v python3 &> /dev/null; then + echo "错误: 未找到 Python3" + exit 1 +fi + +# 启动 API 服务(后台) +echo "启动 API 服务 (端口 8000)..." +python -m uvicorn facebook_agent.api:app --host 0.0.0.0 --port 8000 & +API_PID=$! + +# 等待 API 服务启动 +sleep 2 + +# 启动 MCP HTTP 服务(后台) +echo "启动 MCP HTTP 服务 (端口 8001)..." +python -m uvicorn facebook_agent.mcp_http_server:app --host 0.0.0.0 --port 8001 & +MCP_PID=$! + +# 等待 MCP 服务启动 +sleep 2 + +echo "==========================================" +echo "两个服务已启动" +echo "API 服务 PID: $API_PID (端口 8000)" +echo "MCP 服务 PID: $MCP_PID (端口 8001)" +echo "==========================================" + +# 等待进程 +wait $API_PID $MCP_PID + diff --git a/agent_templates/agents/facebook_agent/start_mcp.sh b/agent_templates/agents/facebook_agent/start_mcp.sh new file mode 100755 index 0000000..16c4c6d --- /dev/null +++ b/agent_templates/agents/facebook_agent/start_mcp.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Facebook搜索Agent MCP服务器启动脚本 + +set -e + +echo "==========================================" +echo "启动Facebook搜索Agent MCP服务器" +echo "==========================================" + +# 检查Python环境 +if ! command -v python3 &> /dev/null; then + echo "错误: 未找到Python3" + exit 1 +fi + +# 检查依赖 +if [ ! -f "requirements.txt" ]; then + echo "错误: 未找到requirements.txt" + exit 1 +fi + +# 检查.env文件 +if [ ! -f ".env" ]; then + echo "警告: 未找到.env文件,将使用默认配置" + echo "请确保设置了必要的环境变量" +fi + +# 选择运行模式 +MODE=${1:-http} + +if [ "$MODE" == "stdio" ]; then + echo "启动stdio模式(本地开发)..." + python3 -m facebook_agent.mcp_server +elif [ "$MODE" == "http" ]; then + echo "启动HTTP模式(远程部署)..." + python3 -m facebook_agent.mcp_http_server +else + echo "用法: $0 [stdio|http]" + echo " stdio - 本地stdio模式(默认)" + echo " http - HTTP模式(远程部署)" + exit 1 +fi + diff --git a/agent_templates/agents/search_agent/search_agent/agent/search_agent.py b/agent_templates/agents/search_agent/search_agent/agent/search_agent.py index f85e3fa..1089ae9 100644 --- a/agent_templates/agents/search_agent/search_agent/agent/search_agent.py +++ b/agent_templates/agents/search_agent/search_agent/agent/search_agent.py @@ -6,8 +6,8 @@ from typing import List, Optional from loguru import logger -from config import Config -from models.schemas import ( +from search_agent.config import Config +from search_agent.models.schemas import ( QueryAnalysis, SearchPlan, SearchResult, @@ -16,13 +16,13 @@ from models.schemas import ( Answer, AgentResponse, ) -from modules.query_analyzer import QueryAnalyzer -from modules.search_planner import SearchPlanner -from modules.search_executor import SearchExecutor -from modules.content_extractor import ContentExtractor -from modules.result_processor import ResultProcessor -from modules.answer_generator import AnswerGenerator -from modules.reflector import Reflector +from search_agent.modules.query_analyzer import QueryAnalyzer +from search_agent.modules.search_planner import SearchPlanner +from search_agent.modules.search_executor import SearchExecutor +from search_agent.modules.content_extractor import ContentExtractor +from search_agent.modules.result_processor import ResultProcessor +from search_agent.modules.answer_generator import AnswerGenerator +from search_agent.modules.reflector import Reflector class SearchAgent: diff --git a/agent_templates/agents/search_agent/search_agent/main.py b/agent_templates/agents/search_agent/search_agent/main.py index ae353d5..a02adb0 100644 --- a/agent_templates/agents/search_agent/search_agent/main.py +++ b/agent_templates/agents/search_agent/search_agent/main.py @@ -6,8 +6,8 @@ import asyncio import sys from loguru import logger -from config import Config -from agent.search_agent import SearchAgent +from search_agent.config import Config +from search_agent.agent.search_agent import SearchAgent def setup_logging(level: str = "INFO"): diff --git a/agent_templates/agents/search_agent/search_agent/modules/answer_generator.py b/agent_templates/agents/search_agent/search_agent/modules/answer_generator.py index 084d5bf..17e565c 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/answer_generator.py +++ b/agent_templates/agents/search_agent/search_agent/modules/answer_generator.py @@ -6,10 +6,10 @@ from typing import List from loguru import logger -from config import Config -from models.schemas import RankedDocument, Answer, Source -from utils.llm_client import LLMClient -from utils.helpers import format_documents_for_prompt +from search_agent.config import Config +from search_agent.models.schemas import RankedDocument, Answer, Source +from search_agent.utils.llm_client import LLMClient +from search_agent.utils.helpers import format_documents_for_prompt # 答案生成Prompt diff --git a/agent_templates/agents/search_agent/search_agent/modules/content_extractor.py b/agent_templates/agents/search_agent/search_agent/modules/content_extractor.py index f94068e..78584c1 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/content_extractor.py +++ b/agent_templates/agents/search_agent/search_agent/modules/content_extractor.py @@ -6,9 +6,9 @@ from typing import List from loguru import logger -from config import Config -from models.schemas import Document, SearchResult, SearchSource -from tools.jina_reader import JinaReaderClient +from search_agent.config import Config +from search_agent.models.schemas import Document, SearchResult, SearchSource +from search_agent.tools.jina_reader import JinaReaderClient class ContentExtractor: diff --git a/agent_templates/agents/search_agent/search_agent/modules/query_analyzer.py b/agent_templates/agents/search_agent/search_agent/modules/query_analyzer.py index 22d2f65..252f8b7 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/query_analyzer.py +++ b/agent_templates/agents/search_agent/search_agent/modules/query_analyzer.py @@ -6,9 +6,9 @@ from typing import Optional from loguru import logger -from config import Config -from models.schemas import QueryAnalysis, Intent -from utils.llm_client import LLMClient +from search_agent.config import Config +from search_agent.models.schemas import QueryAnalysis, Intent +from search_agent.utils.llm_client import LLMClient # 查询分析Prompt diff --git a/agent_templates/agents/search_agent/search_agent/modules/reflector.py b/agent_templates/agents/search_agent/search_agent/modules/reflector.py index 79f41f8..bb7448b 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/reflector.py +++ b/agent_templates/agents/search_agent/search_agent/modules/reflector.py @@ -6,9 +6,9 @@ from typing import List from loguru import logger -from config import Config -from models.schemas import Answer, QualityAssessment -from utils.llm_client import LLMClient +from search_agent.config import Config +from search_agent.models.schemas import Answer, QualityAssessment +from search_agent.utils.llm_client import LLMClient # 反思评估Prompt diff --git a/agent_templates/agents/search_agent/search_agent/modules/result_processor.py b/agent_templates/agents/search_agent/search_agent/modules/result_processor.py index 2ee4f39..0d80e60 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/result_processor.py +++ b/agent_templates/agents/search_agent/search_agent/modules/result_processor.py @@ -6,10 +6,10 @@ from typing import List from loguru import logger -from config import Config -from models.schemas import Document, RankedDocument -from tools.jina_reranker import JinaRerankerClient -from utils.helpers import deduplicate_by_url +from search_agent.config import Config +from search_agent.models.schemas import Document, RankedDocument +from search_agent.tools.jina_reranker import JinaRerankerClient +from search_agent.utils.helpers import deduplicate_by_url class ResultProcessor: diff --git a/agent_templates/agents/search_agent/search_agent/modules/search_executor.py b/agent_templates/agents/search_agent/search_agent/modules/search_executor.py index 27470d8..89f3514 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/search_executor.py +++ b/agent_templates/agents/search_agent/search_agent/modules/search_executor.py @@ -7,9 +7,9 @@ import asyncio from typing import List from loguru import logger -from config import Config -from models.schemas import SearchPlan, SearchTask, SearchResult -from tools.serper import SerperClient +from search_agent.config import Config +from search_agent.models.schemas import SearchPlan, SearchTask, SearchResult +from search_agent.tools.serper import SerperClient class SearchExecutor: diff --git a/agent_templates/agents/search_agent/search_agent/modules/search_planner.py b/agent_templates/agents/search_agent/search_agent/modules/search_planner.py index b70de86..c1c1e76 100644 --- a/agent_templates/agents/search_agent/search_agent/modules/search_planner.py +++ b/agent_templates/agents/search_agent/search_agent/modules/search_planner.py @@ -6,8 +6,8 @@ from typing import List from loguru import logger -from config import Config -from models.schemas import ( +from search_agent.config import Config +from search_agent.models.schemas import ( QueryAnalysis, SearchPlan, SearchTask, diff --git a/agent_templates/agents/search_agent/search_agent/tools/jina_reader.py b/agent_templates/agents/search_agent/search_agent/tools/jina_reader.py index 859e649..a5d12e4 100644 --- a/agent_templates/agents/search_agent/search_agent/tools/jina_reader.py +++ b/agent_templates/agents/search_agent/search_agent/tools/jina_reader.py @@ -8,7 +8,7 @@ from typing import List, Optional import aiohttp from loguru import logger -from models.schemas import Document, SearchSource +from search_agent.models.schemas import Document, SearchSource class JinaReaderClient: diff --git a/agent_templates/agents/search_agent/search_agent/tools/jina_reranker.py b/agent_templates/agents/search_agent/search_agent/tools/jina_reranker.py index 3da52d6..0fb6924 100644 --- a/agent_templates/agents/search_agent/search_agent/tools/jina_reranker.py +++ b/agent_templates/agents/search_agent/search_agent/tools/jina_reranker.py @@ -7,7 +7,7 @@ from typing import List, Tuple import aiohttp from loguru import logger -from models.schemas import Document, RankedDocument +from search_agent.models.schemas import Document, RankedDocument class JinaRerankerClient: diff --git a/agent_templates/agents/search_agent/search_agent/tools/serper.py b/agent_templates/agents/search_agent/search_agent/tools/serper.py index 4e9b673..204f6ca 100644 --- a/agent_templates/agents/search_agent/search_agent/tools/serper.py +++ b/agent_templates/agents/search_agent/search_agent/tools/serper.py @@ -7,7 +7,7 @@ from typing import List, Optional, Dict, Any import aiohttp from loguru import logger -from models.schemas import SearchResult, SearchSource +from search_agent.models.schemas import SearchResult, SearchSource class SerperClient: diff --git a/agent_templates/agents/search_agent/search_agent_A2A/search_agent_A2A.Dockerfile b/agent_templates/agents/search_agent/search_agent_A2A/search_agent_A2A.Dockerfile index 4ed2aaa..4d97a54 100644 --- a/agent_templates/agents/search_agent/search_agent_A2A/search_agent_A2A.Dockerfile +++ b/agent_templates/agents/search_agent/search_agent_A2A/search_agent_A2A.Dockerfile @@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y \ # 复制requirements文件 COPY agents/search_agent/search_agent_A2A/requirements.txt /app/requirements.txt -COPY agents/search_agent/search_agent/search_agent/requirements.txt /app/search_agent_requirements.txt +COPY agents/search_agent/search_agent/requirements.txt /app/search_agent_requirements.txt # 安装Python依赖 # 先安装基础依赖(a2a-sdk的依赖) @@ -38,7 +38,7 @@ RUN if [ -f /app/search_agent_requirements.txt ]; then \ COPY agents/search_agent/search_agent_A2A/ /app/ # 复制search_agent核心代码 -COPY agents/search_agent/search_agent/search_agent/ /app/search_agent/ +COPY agents/search_agent/search_agent/ /app/search_agent/ # 设置环境变量 ENV PYTHONUNBUFFERED=1 diff --git a/agent_templates/agents/search_agent/search_agent_MCP/mcp_server.py b/agent_templates/agents/search_agent/search_agent_MCP/mcp_server.py index d8c28e9..144f00d 100644 --- a/agent_templates/agents/search_agent/search_agent_MCP/mcp_server.py +++ b/agent_templates/agents/search_agent/search_agent_MCP/mcp_server.py @@ -91,14 +91,12 @@ class MCPSearchAgentServer: api_key: LiteLLM API密钥(可选,优先使用,否则从环境变量获取) model: 模型名称(可选,优先使用,否则从环境变量获取) """ - # 获取配置 + # 获取配置(不验证 API key,允许运行时通过 headers 传入) self.llm_config, self.agent_config, self.mcp_config = get_config(api_key, model) - # 创建默认Agent(使用默认配置) - self.default_agent = SearchAgentWrapper( - litellm_config=self.llm_config, - agent_config=self.agent_config - ) + # 不在启动时创建默认 Agent,而是每次请求时根据传入的 API key 创建 + # 这样支持不同用户使用不同的 API key + self.default_agent = None # 任务存储 self.tasks: Dict[str, Dict[str, Any]] = {} @@ -106,6 +104,40 @@ class MCPSearchAgentServer: # 创建FastAPI应用 self.app = self._create_app() + def _extract_api_key_from_headers(self, request: Request) -> Optional[str]: + """ + 从请求 headers 中提取 API key + + 支持两种方式: + 1. api_key header: "api_key: your-api-key-here" + 2. Authorization header: "Authorization: Bearer your-api-key-here" + + Args: + request: FastAPI Request 对象 + + Returns: + 提取的 API key,如果未找到则返回 None + """ + # 方式1: 从 api_key header 获取 + api_key = request.headers.get("api_key") or request.headers.get("api-key") + if api_key: + logger.debug("从 api_key header 获取 API key") + return api_key + + # 方式2: 从 Authorization header 获取 (Bearer token) + auth_header = request.headers.get("Authorization") or request.headers.get("authorization") + if auth_header: + # 支持 "Bearer xxx" 格式 + if auth_header.startswith("Bearer "): + api_key = auth_header[7:] # 去掉 "Bearer " 前缀 + logger.debug("从 Authorization Bearer header 获取 API key") + return api_key + # 也支持直接传入 key(无 Bearer 前缀) + logger.debug("从 Authorization header 获取 API key(无 Bearer 前缀)") + return auth_header + + return None + def _create_app(self) -> FastAPI: """创建FastAPI应用""" @@ -113,7 +145,7 @@ class MCPSearchAgentServer: async def lifespan(app: FastAPI): logger.info("MCP Search Agent服务启动", agent_name=self.agent_config.name) yield - await self.default_agent.close() + # 不需要关闭默认 agent,因为每个请求都创建自己的 agent logger.info("MCP Search Agent服务关闭") app = FastAPI( @@ -137,23 +169,27 @@ class MCPSearchAgentServer: return app - def _get_agent(self, api_key: Optional[str] = None, model: Optional[str] = None) -> SearchAgentWrapper: + def _get_agent(self, api_key: str, model: Optional[str] = None) -> SearchAgentWrapper: """ - 获取Agent实例 + 创建并返回 Agent 实例 - 如果提供了api_key或model,创建新的Agent实例 - 否则使用默认Agent + 每个请求都创建一个新的 Agent 实例,使用请求中传入的 API key + + Args: + api_key: LiteLLM API 密钥(必需) + model: 模型名称(可选,默认从环境变量获取) + + Returns: + SearchAgentWrapper 实例 """ - if api_key or model: - # 创建新的配置和Agent - llm_config, agent_config, _ = get_config(api_key, model) - return SearchAgentWrapper( - litellm_config=llm_config, - agent_config=agent_config, - api_key=api_key, - model=model - ) - return self.default_agent + # 创建新的配置和Agent + llm_config, agent_config, _ = get_config(api_key, model) + return SearchAgentWrapper( + litellm_config=llm_config, + agent_config=agent_config, + api_key=api_key, + model=model + ) def _register_routes(self, app: FastAPI): """注册MCP协议路由""" @@ -203,7 +239,7 @@ class MCPSearchAgentServer: # 处理 search 方法 if rpc_request.method == "search": - return await self._handle_search(rpc_request) + return await self._handle_search(rpc_request, request) else: return JSONResponse({ "jsonrpc": "2.0", @@ -231,7 +267,7 @@ class MCPSearchAgentServer: } }) - return await self._handle_search_stream(rpc_request) + return await self._handle_search_stream(rpc_request, request) @app.post("/mcp/v1/call") async def mcp_call(request: Request): @@ -252,9 +288,9 @@ class MCPSearchAgentServer: # 根据方法名路由 if rpc_request.method == "search": - return await self._handle_search(rpc_request) + return await self._handle_search(rpc_request, request) elif rpc_request.method == "search/stream": - return await self._handle_search_stream(rpc_request) + return await self._handle_search_stream(rpc_request, request) else: return JSONResponse({ "jsonrpc": "2.0", @@ -265,8 +301,13 @@ class MCPSearchAgentServer: } }) - async def _handle_search(self, request: MCPRequest) -> JSONResponse: - """处理 search 请求""" + async def _handle_search(self, request: MCPRequest, http_request: Request) -> JSONResponse: + """处理 search 请求 + + 认证方式(优先级从高到低): + 1. HTTP headers: api_key 或 Authorization: Bearer xxx + 2. JSON body params: api_key 或 llm_api_key(向后兼容) + """ params = request.params or {} # 提取搜索查询 @@ -281,15 +322,19 @@ class MCPSearchAgentServer: } }) - # 提取API key(必须从请求参数中获取,等同于API格式版本的llm_api_key) - api_key = params.get("api_key") or params.get("llm_api_key") + # 提取API key(优先从 headers 获取,其次从 params 获取) + api_key = self._extract_api_key_from_headers(http_request) + if not api_key: + # 向后兼容:从 params 中获取 + api_key = params.get("api_key") or params.get("llm_api_key") + if not api_key: return JSONResponse({ "jsonrpc": "2.0", "id": request.id, "error": { "code": -32602, - "message": "Invalid params: 'api_key' or 'llm_api_key' is required" + "message": "Authentication required: provide 'api_key' header or 'Authorization: Bearer xxx' header" } }) @@ -318,9 +363,8 @@ class MCPSearchAgentServer: response = await agent.search(query=query) - # 如果创建了新Agent,关闭它 - if api_key or model: - await agent.close() + # 关闭 Agent(每个请求都创建新的 Agent) + await agent.close() # 构建响应数据 sources = [] @@ -360,8 +404,13 @@ class MCPSearchAgentServer: } }) - async def _handle_search_stream(self, request: MCPRequest) -> StreamingResponse: - """处理 search/stream 请求 (SSE)""" + async def _handle_search_stream(self, request: MCPRequest, http_request: Request) -> StreamingResponse: + """处理 search/stream 请求 (SSE) + + 认证方式(优先级从高到低): + 1. HTTP headers: api_key 或 Authorization: Bearer xxx + 2. JSON body params: api_key 或 llm_api_key(向后兼容) + """ params = request.params or {} # 提取搜索查询 @@ -376,15 +425,19 @@ class MCPSearchAgentServer: } }) - # 提取API key(必须从请求参数中获取,等同于API格式版本的llm_api_key) - api_key = params.get("api_key") or params.get("llm_api_key") + # 提取API key(优先从 headers 获取,其次从 params 获取) + api_key = self._extract_api_key_from_headers(http_request) + if not api_key: + # 向后兼容:从 params 中获取 + api_key = params.get("api_key") or params.get("llm_api_key") + if not api_key: return JSONResponse({ "jsonrpc": "2.0", "id": request.id, "error": { "code": -32602, - "message": "Invalid params: 'api_key' or 'llm_api_key' is required" + "message": "Authentication required: provide 'api_key' header or 'Authorization: Bearer xxx' header" } }) @@ -492,8 +545,8 @@ class MCPSearchAgentServer: } yield f"data: {json.dumps(error_event)}\n\n" finally: - # 如果创建了新Agent,关闭它 - if agent and (api_key or model): + # 关闭 Agent(每个请求都创建新的 Agent) + if agent: await agent.close() return StreamingResponse( diff --git a/agent_templates/agents/search_agent/search_agent_MCP/search_agent_MCP.Dockerfile b/agent_templates/agents/search_agent/search_agent_MCP/search_agent_MCP.Dockerfile index 1294a71..70cd743 100644 --- a/agent_templates/agents/search_agent/search_agent_MCP/search_agent_MCP.Dockerfile +++ b/agent_templates/agents/search_agent/search_agent_MCP/search_agent_MCP.Dockerfile @@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y \ # 复制requirements文件 COPY agents/search_agent/search_agent_MCP/requirements.txt /app/requirements.txt -COPY agents/search_agent/search_agent/search_agent/requirements.txt /app/search_agent_requirements.txt +COPY agents/search_agent/search_agent/requirements.txt /app/search_agent_requirements.txt # 安装Python依赖 RUN pip install --no-cache-dir \ @@ -23,7 +23,7 @@ RUN pip install --no-cache-dir \ COPY agents/search_agent/search_agent_MCP/ /app/ # 复制search_agent核心代码 -COPY agents/search_agent/search_agent/search_agent/ /app/search_agent/ +COPY agents/search_agent/search_agent/ /app/search_agent/ # 设置环境变量 ENV PYTHONUNBUFFERED=1 diff --git a/agent_templates/scripts/build_all_agents.sh b/agent_templates/scripts/build_all_agents.sh index 1efa7c1..7db7975 100755 --- a/agent_templates/scripts/build_all_agents.sh +++ b/agent_templates/scripts/build_all_agents.sh @@ -43,10 +43,16 @@ docker buildx use multiarch-builder # Agent 列表(更新路径) declare -A AGENTS=( ["search-agent"]="agents/search_agent/search_agent.Dockerfile" + ["search-agent-a2a"]="agents/search_agent/search_agent_A2A/search_agent_A2A.Dockerfile" + ["search-agent-mcp"]="agents/search_agent/search_agent_MCP/search_agent_MCP.Dockerfile" ["azure-blob-agent"]="agents/azure_blob_agent/azure_blob_agent.Dockerfile" ["azure-blob-agent-a2a"]="agents/azure_blob_agent_a2a/azure_blob_agent_a2a.Dockerfile" ["azure-blob-agent-mcp"]="agents/azure_blob_agent_mcp/azure_blob_agent_mcp.Dockerfile" ["a2a-litellm-agent"]="agents/a2a_litellm_agent/a2a_litellm_agent.Dockerfile" + ["mysql-agent"]="agents/mysql_agent/mysql_agent.Dockerfile" + ["postgresql-agent"]="agents/postgresql_agent/postgresql_agent.Dockerfile" + ["jina-search-agent"]="agents/jina_search_agent/jina_search_agent.Dockerfile" + ["echo-agent"]="agents/echo_agent/echo_agent.Dockerfile" ) # 构建函数 diff --git a/app.py b/app.py index 8fb0b45..4eaf067 100644 --- a/app.py +++ b/app.py @@ -300,7 +300,7 @@ async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db logger.info(f"收到创建Agent请求: {request.name}, 模板: {request.template}") # 验证模板类型 - valid_templates = ["echo_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent"] + valid_templates = ["echo_agent", "search_agent", "search_agent_a2a", "search_agent_mcp", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent", "code_ai_agent", "facebook_agent"] if request.template not in valid_templates: raise HTTPException( status_code=400, @@ -577,53 +577,77 @@ async def get_agent_status(agent_name: str, db: Session = Depends(get_db)): try: logger.info(f"获取Agent状态: {agent_name}") - # 从Kubernetes获取Pod状态 - result = k8s_manager.get_pod_status(pod_name=agent_name) + # 先从数据库获取Agent的namespace + db_agent = db.query(Agent).filter(Agent.name == agent_name).first() + + # 确定Agent所在的namespace + agent_namespace = None + if db_agent and db_agent.namespace: + agent_namespace = db_agent.namespace + else: + # 如果数据库中没有,尝试查找以 agent-{agent_name} 开头的命名空间 + try: + namespaces = k8s_manager.v1.list_namespace( + label_selector=f"agent-name={agent_name}" + ) + if namespaces.items: + agent_namespace = namespaces.items[0].metadata.name + else: + # 尝试常见的命名空间格式 + for ns_pattern in [f"agent-{agent_name}", f"agent-test-{agent_name}"]: + try: + k8s_manager.v1.read_namespace(name=ns_pattern) + agent_namespace = ns_pattern + break + except: + continue + except Exception as e: + logger.warning(f"查找命名空间失败: {e}") + + if not agent_namespace: + raise HTTPException(status_code=404, detail=f"Agent {agent_name} 的命名空间未找到") + + # 使用正确的namespace获取Pod状态 + temp_manager = K8sManager(namespace=agent_namespace, kubeconfig_path=KUBECONFIG_PATH) + result = temp_manager.get_pod_status(pod_name=agent_name) if result.get("status") == "not_found": raise HTTPException(status_code=404, detail=result.get("message")) - # 从数据库获取Agent记录(包含访问信息) - try: - db_agent = db.query(Agent).filter(Agent.name == agent_name).first() + # 添加数据库中的信息 + if db_agent: + # 添加框架类型 + result["framework"] = db_agent.agent_framework.upper() if db_agent.agent_framework else "API" - if db_agent: - # 添加框架类型 - result["framework"] = db_agent.agent_framework.upper() if db_agent.agent_framework else "API" - - # 添加访问信息 - access_info = {} - - if db_agent.external_ip: - access_info["external_ip"] = db_agent.external_ip - - if db_agent.ip_url: - access_info["ip_url"] = db_agent.ip_url - - if db_agent.domain: - access_info["domain"] = db_agent.domain - - if db_agent.domain_url: - access_info["domain_url"] = db_agent.domain_url - - if db_agent.recommended_url: - access_info["recommended_url"] = db_agent.recommended_url - - if db_agent.service_name: - access_info["service_name"] = db_agent.service_name - - # 只有当有访问信息时才添加 - if access_info: - result["access_info"] = access_info - logger.info(f"✅ 已添加访问信息: domain={db_agent.domain}, ip={db_agent.external_ip}") - else: - logger.warning(f"⚠️ Agent {agent_name} 在数据库中没有访问信息") + # 添加访问信息 + access_info = {} + + if db_agent.external_ip: + access_info["external_ip"] = db_agent.external_ip + + if db_agent.ip_url: + access_info["ip_url"] = db_agent.ip_url + + if db_agent.domain: + access_info["domain"] = db_agent.domain + + if db_agent.domain_url: + access_info["domain_url"] = db_agent.domain_url + + if db_agent.recommended_url: + access_info["recommended_url"] = db_agent.recommended_url + + if db_agent.service_name: + access_info["service_name"] = db_agent.service_name + + # 只有当有访问信息时才添加 + if access_info: + result["access_info"] = access_info + logger.info(f"✅ 已添加访问信息: domain={db_agent.domain}, ip={db_agent.external_ip}") else: - logger.warning(f"⚠️ Agent {agent_name} 在数据库中未找到,可能是在数据库启用前创建的") - - except Exception as db_error: - logger.error(f"从数据库读取访问信息失败: {str(db_error)}") - # 不抛出异常,继续返回Pod状态信息 + logger.warning(f"⚠️ Agent {agent_name} 在数据库中没有访问信息") + else: + logger.warning(f"⚠️ Agent {agent_name} 在数据库中未找到") return PodStatusResponse(**result) @@ -635,33 +659,70 @@ async def get_agent_status(agent_name: str, db: Session = Depends(get_db)): @app.get("/agents/{agent_name}/metrics", response_model=PodMetricsResponse) -async def get_agent_metrics(agent_name: str): +async def get_agent_metrics(agent_name: str, db: Session = Depends(get_db)): """ 获取Agent资源使用情况 Args: agent_name: Agent名称 + db: 数据库会话 Returns: Agent资源使用信息 """ try: logger.info(f"获取Agent资源信息: {agent_name}") - result = k8s_manager.get_pod_metrics(pod_name=agent_name) + + # 先从数据库获取Agent的namespace + db_agent = db.query(Agent).filter(Agent.name == agent_name).first() + + # 确定Agent所在的namespace + agent_namespace = None + if db_agent and db_agent.namespace: + agent_namespace = db_agent.namespace + else: + # 如果数据库中没有,尝试查找以 agent-{agent_name} 开头的命名空间 + try: + namespaces = k8s_manager.v1.list_namespace( + label_selector=f"agent-name={agent_name}" + ) + if namespaces.items: + agent_namespace = namespaces.items[0].metadata.name + else: + # 尝试常见的命名空间格式 + for ns_pattern in [f"agent-{agent_name}", f"agent-test-{agent_name}"]: + try: + k8s_manager.v1.read_namespace(name=ns_pattern) + agent_namespace = ns_pattern + break + except: + continue + except Exception as e: + logger.warning(f"查找命名空间失败: {e}") + + if not agent_namespace: + raise HTTPException(status_code=404, detail=f"Agent {agent_name} 的命名空间未找到") + + # 使用正确的namespace获取Pod指标 + temp_manager = K8sManager(namespace=agent_namespace, kubeconfig_path=KUBECONFIG_PATH) + result = temp_manager.get_pod_metrics(pod_name=agent_name) return PodMetricsResponse(**result) + except HTTPException: + raise except Exception as e: logger.error(f"获取Agent资源信息失败: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @app.get("/agents") -async def list_agents(template: Optional[str] = None): +async def list_agents(template: Optional[str] = None, db: Session = Depends(get_db)): """ - 列出所有Agent + 列出所有Agent(跨所有命名空间) Args: template: 模板类型过滤(可选) + db: 数据库会话 Returns: Agent列表 @@ -669,12 +730,94 @@ async def list_agents(template: Optional[str] = None): try: logger.info(f"列出Agents, 模板过滤: {template}") - label_selector = "managed-by=agent-manager" - if template: - label_selector += f",template={template}" + all_agents = [] - result = k8s_manager.list_pods(label_selector=label_selector) - return {"agents": result, "count": len(result)} + # 方法1: 从数据库获取Agent列表(推荐) + try: + query = db.query(Agent) + db_agents = query.all() + + for db_agent in db_agents: + # 尝试从K8s获取Pod状态 + pod_status = "Unknown" + pod_ip = None + try: + if db_agent.namespace: + temp_manager = K8sManager(namespace=db_agent.namespace, kubeconfig_path=KUBECONFIG_PATH) + pod = temp_manager.v1.read_namespaced_pod( + name=db_agent.name, + namespace=db_agent.namespace + ) + pod_status = pod.status.phase + pod_ip = pod.status.pod_ip + except Exception: + pod_status = "NotFound" + + agent_info = { + "name": db_agent.name, + "namespace": db_agent.namespace, + "status": pod_status, + "template": db_agent.agent_framework or "unknown", + "created_at": db_agent.created_at.isoformat() if db_agent.created_at else None, + "pod_ip": pod_ip, + "external_ip": db_agent.external_ip, + "domain": db_agent.domain, + "service_url": db_agent.recommended_url + } + + # 应用模板过滤 + if template and agent_info.get("template") != template: + continue + + all_agents.append(agent_info) + + logger.info(f"从数据库获取到 {len(all_agents)} 个Agent") + + except Exception as db_error: + logger.warning(f"从数据库获取Agent列表失败: {db_error}") + + # 方法2: 遍历所有以 agent- 开头的命名空间(作为补充) + try: + namespaces = k8s_manager.v1.list_namespace() + agent_namespaces = [ + ns.metadata.name for ns in namespaces.items + if ns.metadata.name.startswith("agent-") + ] + + # 已经从数据库获取的Agent名称 + known_agents = {a["name"] for a in all_agents} + + for ns_name in agent_namespaces: + try: + temp_manager = K8sManager(namespace=ns_name, kubeconfig_path=KUBECONFIG_PATH) + label_selector = "managed-by=agent-manager" + if template: + label_selector += f",template={template}" + + pods = temp_manager.v1.list_namespaced_pod( + namespace=ns_name, + label_selector=label_selector + ) + + for pod in pods.items: + if pod.metadata.name not in known_agents: + agent_info = { + "name": pod.metadata.name, + "namespace": ns_name, + "status": pod.status.phase, + "template": pod.metadata.labels.get("template", "unknown"), + "created_at": pod.metadata.creation_timestamp.isoformat() if pod.metadata.creation_timestamp else None, + "pod_ip": pod.status.pod_ip + } + all_agents.append(agent_info) + + except Exception as e: + logger.debug(f"命名空间 {ns_name} 查询失败: {e}") + + except Exception as ns_error: + logger.warning(f"遍历命名空间失败: {ns_error}") + + return {"agents": all_agents, "count": len(all_agents)} except Exception as e: logger.error(f"列出Agents失败: {str(e)}") @@ -689,7 +832,7 @@ async def list_templates(): Returns: 模板列表及其配置信息 """ - valid_templates = ["echo_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent"] + valid_templates = ["echo_agent", "search_agent", "search_agent_a2a", "search_agent_mcp", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent", "code_ai_agent", "facebook_agent"] templates_info = [] for template in valid_templates: @@ -711,7 +854,7 @@ async def list_platform_templates(): 平台提供的Agent模板列表 """ # 平台 Agent 是预定义的标准模板 - platform_templates = ["echo_agent", "search_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent"] + platform_templates = ["echo_agent", "search_agent", "search_agent_a2a", "search_agent_mcp", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent", "code_ai_agent", "facebook_agent"] templates_info = [] for template in platform_templates: @@ -761,7 +904,7 @@ async def get_template_info(template_name: str): Returns: 模板详细信息(端口、所需环境变量等) """ - valid_templates = ["echo_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent"] + valid_templates = ["echo_agent", "search_agent", "search_agent_a2a", "search_agent_mcp", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "a2a_litellm_agent", "code_ai_agent", "facebook_agent"] if template_name not in valid_templates: raise HTTPException( diff --git a/cursor-mcp-config-agents.json b/cursor-mcp-config-agents.json new file mode 100644 index 0000000..0662c62 --- /dev/null +++ b/cursor-mcp-config-agents.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "code-ai-agent": { + "url": "http://test-code-ai-agent-4.taijiagnet.com/mcp", + "type": "http", + "description": "代码助手 Agent - 支持代码生成、重构、审查和组织功能。提供以下工具:generate_code, refactor_code, review_code, organize_code, classify_code, analyze_project, suggest_folder_structure, create_code_file" + }, + "facebook-agent": { + "url": "http://test-facebook-agent-6.taijiagnet.com/mcp", + "type": "http", + "description": "Facebook 搜索 Agent - 支持 Facebook 内容搜索,返回相关帖子和AI生成的总结" + } + } +} diff --git a/k8s_manager.py b/k8s_manager.py index 4357a66..a3bca97 100644 --- a/k8s_manager.py +++ b/k8s_manager.py @@ -314,6 +314,8 @@ class K8sManager: TEMPLATE_PORTS = { "echo_agent": 8000, "search_agent": 8080, + "search_agent_a2a": 8080, + "search_agent_mcp": 8080, "mysql_agent": 8000, "postgresql_agent": 8000, "jina_search_agent": 8080, @@ -321,6 +323,8 @@ class K8sManager: "azure_blob_agent_mcp": 8080, "azure_blob_agent_a2a": 8080, "a2a_litellm_agent": 8080, + "code_ai_agent": 8000, + "facebook_agent": 8000, } # 模板所需环境变量说明 @@ -417,7 +421,7 @@ class K8sManager: }, "optional": { "LLM_API_KEY": "LLM API 密钥(可在搜索请求中传入)", - "LLM_MODEL": "LLM 模型名称,默认 gpt-4o-mini", + "MODEL_NAME": "LLM 模型名称,默认 gpt-4o-mini", "MAX_ITERATIONS": "最大搜索迭代次数,默认 3", "MAX_RESULTS_PER_QUERY": "每次搜索最大结果数,默认 10", "CONTENT_MAX_LENGTH": "内容最大长度,默认 5000", @@ -426,6 +430,40 @@ class K8sManager: "SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0" } }, + "search_agent_a2a": { + "required": { + "LLM_BASE_URL": "LLM 服务地址,如 https://api.openai.com/v1", + }, + "optional": { + "LITELLM_API_KEY": "LiteLLM API 密钥(可在请求中传入)", + "LLM_API_KEY": "LLM API 密钥(备选,可在请求中传入)", + "MODEL_NAME": "LLM 模型名称(优先)", + "LLM_MODEL": "LLM 模型名称(备选)", + "LITELLM_MODEL": "LiteLLM 模型名称(备选)", + "SERPER_API_KEY": "Serper 搜索 API 密钥(已内置默认值)", + "JINA_API_KEY": "Jina Reader API 密钥(已内置默认值)", + "SERVICE_PORT": "HTTP服务端口,默认 8080", + "SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0" + }, + "description": "A2A 协议搜索 Agent,支持通过请求动态传入 API key" + }, + "search_agent_mcp": { + "required": { + "LLM_BASE_URL": "LLM 服务地址,如 https://api.openai.com/v1", + }, + "optional": { + "LITELLM_API_KEY": "LiteLLM API 密钥(可在请求中传入)", + "LLM_API_KEY": "LLM API 密钥(备选,可在请求中传入)", + "MODEL_NAME": "LLM 模型名称(优先)", + "LLM_MODEL": "LLM 模型名称(备选)", + "LITELLM_MODEL": "LiteLLM 模型名称(备选)", + "SERPER_API_KEY": "Serper 搜索 API 密钥(已内置默认值)", + "JINA_API_KEY": "Jina Reader API 密钥(已内置默认值)", + "SERVICE_PORT": "HTTP服务端口,默认 8080", + "SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0" + }, + "description": "MCP 协议搜索 Agent,支持通过请求动态传入 API key" + }, "a2a_litellm_agent": { "required": { "LITELLM_API_BASE": "LiteLLM 服务地址", @@ -438,6 +476,35 @@ class K8sManager: "SERVICE_PORT": "HTTP服务端口,默认 8080", "SERVICE_HOST": "HTTP服务监听地址,默认 0.0.0.0" } + }, + "code_ai_agent": { + "required": { + "LLM_BASE_URL": "LLM 服务地址,如 https://api.openai.com/v1", + }, + "optional": { + "LLM_API_KEY": "LLM API 密钥(可在请求中传入)", + "MODEL_NAME": "LLM 模型名称", + "API_HOST": "API 服务监听地址,默认 0.0.0.0", + "API_PORT": "API 服务端口,默认 8000", + "PROJECTS_DIR": "项目存储目录,默认 /tmp/projects" + }, + "description": "代码助手 Agent,支持代码生成、分析和执行" + }, + "facebook_agent": { + "required": { + "LITELLM_GATEWAY_URL": "LiteLLM Gateway 服务地址,如 https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/", + "FACEBOOK_API_KEY": "Facebook RapidAPI 密钥" + }, + "optional": { + "LITELLM_API_KEY": "LiteLLM API 密钥(可在请求中传入)", + "LITELLM_MODEL": "LiteLLM 模型名称,默认 taiji/gpt-4o-mini", + "FACEBOOK_API_HOST": "Facebook API 主机,默认 facebook-scraper3.p.rapidapi.com", + "FACEBOOK_MCP_URL": "Facebook MCP URL,默认 https://mcp.rapidapi.com", + "MAX_RESULTS": "最大搜索结果数,默认 10", + "TIMEOUT": "超时时间(秒),默认 30", + "LOG_LEVEL": "日志级别,默认 INFO" + }, + "description": "Facebook 搜索智能 Agent,支持 Facebook 内容搜索和 MCP 协议" } } @@ -523,6 +590,8 @@ class K8sManager: image_map = { "echo_agent": "agnettaiji.azurecr.io/echo-agent:latest", "search_agent": "agnettaiji.azurecr.io/ai-agents/search-agent:latest", + "search_agent_a2a": "agnettaiji.azurecr.io/ai-agents/search-agent-a2a:latest", + "search_agent_mcp": "agnettaiji.azurecr.io/ai-agents/search-agent-mcp:latest", "mysql_agent": "agnettaiji.azurecr.io/ai-agents/mysql-agent:latest", "postgresql_agent": "agnettaiji.azurecr.io/ai-agents/postgresql-agent:latest", "jina_search_agent": "agnettaiji.azurecr.io/ai-agents/jina-search-agent:latest", @@ -530,6 +599,8 @@ class K8sManager: "azure_blob_agent_mcp": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-mcp:latest", "azure_blob_agent_a2a": "agnettaiji.azurecr.io/ai-agents/azure-blob-agent-a2a:latest", "a2a_litellm_agent": "agnettaiji.azurecr.io/ai-agents/a2a-litellm-agent:latest", + "code_ai_agent": "agnettaiji.azurecr.io/ai-agents/code-ai-agent:latest", + "facebook_agent": "agnettaiji.azurecr.io/ai-agents/facebook-agent:latest", } image = image_map.get(template, image_map["search_agent"]) @@ -591,8 +662,32 @@ class K8sManager: namespace = config_data.get("namespace", self.namespace) env_vars.append(client.V1EnvVar(name="NAMESPACE", value=namespace)) - # 添加用户自定义环境变量 + # 为特定模板添加默认环境变量 + template_defaults = { + "facebook_agent": { + "LITELLM_GATEWAY_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io", + "LITELLM_MODEL": "taiji/gpt-4o-mini", + "FACEBOOK_API_KEY": "34225c5924msh453fa7aff7a52a9p1d7adfjsn260ba0796c00" + }, + "code_ai_agent": { + "OPENAI_BASE_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1", + "LITELLM_MODEL": "taiji/gpt-4o-mini" + } + } + + # 获取用户自定义环境变量 custom_env = config_data.get("env", {}) + + # 如果模板有默认值,先添加默认值(用户自定义值会覆盖) + if template in template_defaults: + defaults = template_defaults[template] + for key, value in defaults.items(): + # 只有当用户没有提供该环境变量时才使用默认值 + if key not in custom_env: + env_vars.append(client.V1EnvVar(name=key, value=str(value))) + logger.info(f"使用默认环境变量: {key}") + + # 添加用户自定义环境变量(会覆盖默认值) for key, value in custom_env.items(): env_vars.append(client.V1EnvVar(name=key, value=str(value))) @@ -600,8 +695,10 @@ class K8sManager: # 设置容器端口(如果是HTTP服务类型的agent) container_ports = None - if template in ["jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a"]: + if template in ["jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a", "search_agent", "search_agent_a2a", "search_agent_mcp", "a2a_litellm_agent"]: container_ports = [client.V1ContainerPort(container_port=8080)] + elif template in ["code_ai_agent", "facebook_agent", "echo_agent", "mysql_agent", "postgresql_agent"]: + container_ports = [client.V1ContainerPort(container_port=8000)] # 创建Pod规格 container = client.V1Container(