diff --git a/search_agent/a2a-ai-search-agent/.gitignore b/search_agent/a2a-ai-search-agent/.gitignore deleted file mode 100644 index 1a1a74a..0000000 --- a/search_agent/a2a-ai-search-agent/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Python -__pycache__/ -*.py[cod] -*.class -*.so -.Python -venv/ -.venv/ -ENV/ -env/ - -# IDE -.vscode/ -.idea/ -*.swp -*.swo - -# 环境变量 -.env -*.log - -# 系统文件 -.DS_Store -Thumbs.db diff --git a/search_agent/a2a-ai-search-agent/Dockerfile b/search_agent/a2a-ai-search-agent/Dockerfile deleted file mode 100644 index 0753eeb..0000000 --- a/search_agent/a2a-ai-search-agent/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM python:3.12-slim - -WORKDIR /app - -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 - -RUN apt-get update && apt-get install -y --no-install-recommends curl \ - && rm -rf /var/lib/apt/lists/* - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY config.py agent.py a2a_server.py ./ - -RUN useradd -m -u 1000 agent && chown -R agent:agent /app -USER agent - -EXPOSE 9000 - -HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ - CMD curl -f http://localhost:9000/health || exit 1 - -CMD ["uvicorn", "a2a_server:app", "--host", "0.0.0.0", "--port", "9000"] diff --git a/search_agent/a2a-ai-search-agent/README.md b/search_agent/a2a-ai-search-agent/README.md deleted file mode 100644 index cc885e7..0000000 --- a/search_agent/a2a-ai-search-agent/README.md +++ /dev/null @@ -1,181 +0,0 @@ -# A2A AI Search Agent - -基于 LiteLLM 和 A2A (Agent2Agent) 协议的智能搜索 Agent API 服务。 - -## 🚀 功能特性 - -- ✅ **A2A 协议兼容** - 完全支持 Google Agent2Agent 协议 -- ✅ **多轮对话** - 维护对话上下文,支持连续对话 -- ✅ **流式响应** - 支持 SSE 流式输出 -- ✅ **LiteLLM 集成** - 统一的 LLM 调用接口 -- ✅ **生产就绪** - 适用于 AKS (Azure Kubernetes Service) 部署 - -## 📁 项目结构 - -``` -a2a-ai-search-agent/ -├── config.py # 配置管理 -├── agent.py # Agent 核心逻辑 -├── a2a_server.py # A2A 协议 API 服务 -├── requirements.txt # Python 依赖 -├── Dockerfile # Docker 镜像 -├── deploy.sh # 部署脚本 -└── k8s/ # Kubernetes 配置 - ├── deployment.yaml - ├── service.yaml - ├── configmap.yaml - └── secret.yaml -``` - -## 🔧 配置 - -**环境变量:** -- `LITELLM_API_KEY` - LiteLLM API 密钥(必需) -- `LITELLM_MODEL` - 模型名称(如: taiji/gpt-4o-mini) - -**LiteLLM 服务地址:** -``` -https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io -``` - -## 🐳 Docker 部署 - -### 构建镜像 - -```bash -docker build -t a2a-ai-search-agent:latest . -``` - -### 运行容器 - -```bash -docker run -d \ - -p 9000:9000 \ - -e LITELLM_API_KEY="your-api-key" \ - -e LITELLM_MODEL="taiji/gpt-4o-mini" \ - a2a-ai-search-agent:latest -``` - -## ☸️ Kubernetes 部署 - -### 推送到 ACR - -```bash -# 登录 ACR -docker login agnettaiji.azurecr.io -u agnettaiji -p 'YOUR_PASSWORD' - -# 打标签 -docker tag a2a-ai-search-agent:latest agnettaiji.azurecr.io/xiaohei-agent:latest - -# 推送 -docker push agnettaiji.azurecr.io/xiaohei-agent:latest -``` - -### 部署到 AKS - -```bash -# 方式1: 使用脚本 -./deploy.sh - -# 方式2: 手动部署 -kubectl apply -f k8s/configmap.yaml -kubectl apply -f k8s/secret.yaml -kubectl apply -f k8s/deployment.yaml -kubectl apply -f k8s/service.yaml -``` - -### 查看部署状态 - -```bash -# 查看 Pod -kubectl get pods -l app=xiaohei-agent - -# 查看 Service 和外网 IP -kubectl get svc xiaohei-agent-service - -# 实时监控外网 IP 分配 -kubectl get svc xiaohei-agent-service -w -``` - -## 📡 API 端点 - -### 健康检查 -```bash -GET /health -``` - -### Agent Card (A2A 发现) -```bash -GET /.well-known/agent.json -``` - -**响应示例:** -```json -{ - "name": "xiaohei-agent", - "description": "一个基于LiteLLM的智能Agent,支持A2A协议", - "version": "1.0.0", - "capabilities": { - "text": true, - "streaming": true, - "push_notifications": false - }, - "skills": [ - { - "id": "general-assistant", - "name": "通用助手", - "description": "回答问题、提供建议、协助完成各种任务" - } - ] -} -``` - -### 发送消息 (非流式) - -```bash -POST /message/send -Content-Type: application/json - -{ - "jsonrpc": "2.0", - "id": "request-1", - "method": "message/send", - "params": { - "message": { - "role": "user", - "parts": [{"kind": "text", "text": "你好"}], - "messageId": "msg-1" - } - } -} -``` - -### 发送消息 (流式) - -```bash -POST /message/stream -Content-Type: application/json - -{ - "jsonrpc": "2.0", - "id": "request-1", - "method": "message/stream", - "params": { - "message": { - "role": "user", - "parts": [{"kind": "text", "text": "你好"}], - "messageId": "msg-1" - } - } -} -``` - -## 🔗 相关资源 - -- [A2A 协议规范](https://github.com/a2aproject/A2A) -- [LiteLLM 文档](https://docs.litellm.ai/) -- [Google Agent2Agent 介绍](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability) - -## 📄 许可证 - -MIT License diff --git a/search_agent/a2a-ai-search-agent/a2a_server.py b/search_agent/a2a-ai-search-agent/a2a_server.py deleted file mode 100644 index 4e9d2c6..0000000 --- a/search_agent/a2a-ai-search-agent/a2a_server.py +++ /dev/null @@ -1,487 +0,0 @@ -""" -A2A协议兼容的Agent服务 -实现Google Agent2Agent协议规范 -""" - -import asyncio -import json -import uuid -from typing import Optional, Dict, Any, AsyncGenerator -from datetime import datetime -from contextlib import asynccontextmanager - -from fastapi import FastAPI, HTTPException, Request, Response -from fastapi.responses import StreamingResponse, JSONResponse -from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel, Field -import structlog - -from agent import LiteLLMAgent -from config import get_config, AgentConfig, A2AConfig - -# 配置日志 -logger = structlog.get_logger() - - -# ============== A2A 协议数据模型 ============== - -class A2APart(BaseModel): - """A2A消息部分""" - kind: str = "text" - text: Optional[str] = None - data: Optional[Dict[str, Any]] = None - mime_type: Optional[str] = None - - -class A2AMessage(BaseModel): - """A2A消息""" - role: str - parts: list[A2APart] - messageId: str = Field(default_factory=lambda: uuid.uuid4().hex) - - -class A2AMessageSendParams(BaseModel): - """A2A发送消息参数""" - message: A2AMessage - configuration: Optional[Dict[str, Any]] = None - - -class A2ARequest(BaseModel): - """A2A JSON-RPC请求""" - jsonrpc: str = "2.0" - id: str - method: str - params: Optional[Dict[str, Any]] = None - - -class A2AArtifact(BaseModel): - """A2A响应工件""" - artifactId: str = Field(default_factory=lambda: uuid.uuid4().hex) - name: str = "response" - parts: list[A2APart] - - -class A2ATaskStatus(BaseModel): - """A2A任务状态""" - state: str # submitted, working, input-required, completed, failed, canceled - timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat() + "Z") - message: Optional[str] = None - - -class A2ATask(BaseModel): - """A2A任务""" - kind: str = "task" - id: str = Field(default_factory=lambda: uuid.uuid4().hex) - contextId: str = Field(default_factory=lambda: uuid.uuid4().hex) - status: A2ATaskStatus - artifacts: Optional[list[A2AArtifact]] = None - - -class A2AResponse(BaseModel): - """A2A JSON-RPC响应""" - jsonrpc: str = "2.0" - id: str - result: Optional[A2ATask] = None - error: Optional[Dict[str, Any]] = None - - -class A2AStreamEvent(BaseModel): - """A2A流式事件""" - kind: str - taskId: str - contextId: str - data: Optional[Dict[str, Any]] = None - - -# ============== Agent Card ============== - -class AgentSkill(BaseModel): - """Agent技能""" - id: str - name: str - description: str - inputSchema: Optional[Dict[str, Any]] = None - outputSchema: Optional[Dict[str, Any]] = None - - -class AgentCapabilities(BaseModel): - """Agent能力""" - text: bool = True - streaming: bool = True - push_notifications: bool = False - forms: bool = False - files: bool = False - - -class AgentCard(BaseModel): - """A2A Agent Card - 描述Agent能力""" - name: str - description: str - version: str - url: str - capabilities: AgentCapabilities - skills: list[AgentSkill] - authentication: Optional[Dict[str, Any]] = None - - -# ============== A2A Server ============== - -class A2AAgentServer: - """A2A协议Agent服务器""" - - def __init__( - self, - api_key: Optional[str] = None, - model: Optional[str] = None - ): - """ - 初始化A2A Agent服务器 - - Args: - api_key: LiteLLM API密钥 - model: 模型名称 - """ - # 获取配置 - self.llm_config, self.agent_config, self.a2a_config = get_config(api_key, model) - - # 创建Agent - self.agent = LiteLLMAgent( - litellm_config=self.llm_config, - agent_config=self.agent_config - ) - - # 任务存储 - self.tasks: Dict[str, A2ATask] = {} - - # 创建FastAPI应用 - self.app = self._create_app() - - def _create_app(self) -> FastAPI: - """创建FastAPI应用""" - - @asynccontextmanager - async def lifespan(app: FastAPI): - logger.info("A2A Agent服务启动", agent_name=self.agent_config.name) - yield - await self.agent.close() - logger.info("A2A Agent服务关闭") - - app = FastAPI( - title=f"{self.agent_config.name} - A2A Agent", - description=self.agent_config.description, - version=self.agent_config.version, - lifespan=lifespan - ) - - # CORS中间件 - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - # 注册路由 - self._register_routes(app) - - return app - - def _register_routes(self, app: FastAPI): - """注册A2A协议路由""" - - @app.get("/") - async def root(): - """服务根路径""" - return { - "name": self.agent_config.name, - "version": self.agent_config.version, - "protocol": "A2A", - "status": "running" - } - - @app.get("/.well-known/agent.json") - async def get_agent_card(request: Request): - """获取Agent Card (A2A发现协议)""" - base_url = str(request.base_url).rstrip("/") - - card = AgentCard( - name=self.agent_config.name, - description=self.agent_config.description, - version=self.agent_config.version, - url=base_url, - capabilities=AgentCapabilities( - text=True, - streaming=self.agent_config.enable_streaming, - push_notifications=False - ), - skills=[ - AgentSkill( - id="general-assistant", - name="通用助手", - description="回答问题、提供建议、协助完成各种任务" - ), - AgentSkill( - id="code-helper", - name="代码助手", - description="编写、解释和调试代码" - ) - ] - ) - return card.model_dump() - - @app.post("/message/send") - async def send_message(request: Request): - """A2A message/send 端点""" - body = await request.json() - - # 解析JSON-RPC请求 - try: - rpc_request = A2ARequest(**body) - except Exception as e: - return JSONResponse({ - "jsonrpc": "2.0", - "id": body.get("id", "unknown"), - "error": { - "code": -32600, - "message": f"Invalid Request: {str(e)}" - } - }) - - # 处理 message/send 方法 - if rpc_request.method == "message/send": - return await self._handle_message_send(rpc_request) - elif rpc_request.method == "message/stream": - return await self._handle_message_stream(rpc_request) - else: - return JSONResponse({ - "jsonrpc": "2.0", - "id": rpc_request.id, - "error": { - "code": -32601, - "message": f"Method not found: {rpc_request.method}" - } - }) - - @app.post("/message/stream") - async def stream_message(request: Request): - """A2A message/stream 端点 (SSE流式响应)""" - body = await request.json() - - try: - rpc_request = A2ARequest(**body) - except Exception as e: - return JSONResponse({ - "jsonrpc": "2.0", - "id": body.get("id", "unknown"), - "error": { - "code": -32600, - "message": f"Invalid Request: {str(e)}" - } - }) - - return await self._handle_message_stream(rpc_request) - - @app.get("/tasks/{task_id}") - async def get_task(task_id: str): - """获取任务状态""" - if task_id not in self.tasks: - raise HTTPException(status_code=404, detail="Task not found") - return self.tasks[task_id].model_dump() - - @app.get("/health") - async def health_check(): - """健康检查""" - return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()} - - async def _handle_message_send(self, request: A2ARequest) -> JSONResponse: - """处理 message/send 请求""" - params = request.params or {} - message_data = params.get("message", {}) - - # 提取用户消息文本 - user_text = "" - parts = message_data.get("parts", []) - for part in parts: - if part.get("kind") == "text": - user_text += part.get("text", "") - - if not user_text: - return JSONResponse({ - "jsonrpc": "2.0", - "id": request.id, - "error": { - "code": -32602, - "message": "Invalid params: no text content found" - } - }) - - # 创建任务 - task_id = uuid.uuid4().hex - context_id = params.get("contextId", uuid.uuid4().hex) - - task = A2ATask( - id=task_id, - contextId=context_id, - status=A2ATaskStatus(state="working") - ) - self.tasks[task_id] = task - - try: - # 调用Agent获取响应 - logger.info("处理消息", task_id=task_id, message_preview=user_text[:50]) - - response_text = await self.agent.chat( - message=user_text, - conversation_id=context_id - ) - - # 更新任务状态 - task.status = A2ATaskStatus(state="completed") - task.artifacts = [ - A2AArtifact( - name="response", - parts=[A2APart(kind="text", text=response_text)] - ) - ] - self.tasks[task_id] = task - - return JSONResponse({ - "jsonrpc": "2.0", - "id": request.id, - "result": task.model_dump() - }) - - except Exception as e: - logger.error("处理消息失败", error=str(e)) - task.status = A2ATaskStatus(state="failed", message=str(e)) - self.tasks[task_id] = task - - return JSONResponse({ - "jsonrpc": "2.0", - "id": request.id, - "error": { - "code": -32000, - "message": f"Agent error: {str(e)}" - } - }) - - async def _handle_message_stream(self, request: A2ARequest) -> StreamingResponse: - """处理 message/stream 请求 (SSE)""" - params = request.params or {} - message_data = params.get("message", {}) - - # 提取用户消息 - user_text = "" - parts = message_data.get("parts", []) - for part in parts: - if part.get("kind") == "text": - user_text += part.get("text", "") - - task_id = uuid.uuid4().hex - context_id = params.get("contextId", uuid.uuid4().hex) - - async def event_generator() -> AsyncGenerator[str, None]: - """生成SSE事件流""" - # 发送任务开始事件 - start_event = { - "kind": "task-start", - "taskId": task_id, - "contextId": context_id - } - yield f"data: {json.dumps(start_event)}\n\n" - - try: - # 获取流式响应 - stream = await self.agent.chat( - message=user_text, - conversation_id=context_id, - stream=True - ) - - full_response = "" - async for chunk in stream: - full_response += chunk - # 发送文本增量事件 - delta_event = { - "kind": "artifact-delta", - "taskId": task_id, - "contextId": context_id, - "data": { - "kind": "text", - "text": chunk - } - } - yield f"data: {json.dumps(delta_event)}\n\n" - - # 发送完成事件 - complete_event = { - "kind": "task-complete", - "taskId": task_id, - "contextId": context_id, - "data": { - "status": "completed", - "artifacts": [{ - "name": "response", - "parts": [{"kind": "text", "text": full_response}] - }] - } - } - yield f"data: {json.dumps(complete_event)}\n\n" - - except Exception as e: - # 发送错误事件 - error_event = { - "kind": "task-error", - "taskId": task_id, - "contextId": context_id, - "data": { - "error": str(e) - } - } - yield f"data: {json.dumps(error_event)}\n\n" - - return StreamingResponse( - event_generator(), - media_type="text/event-stream", - headers={ - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "X-Accel-Buffering": "no" - } - ) - - def run(self, host: Optional[str] = None, port: Optional[int] = None): - """运行服务器""" - import uvicorn - - host = host or self.agent_config.host - port = port or self.agent_config.port - - logger.info(f"启动A2A Agent服务", host=host, port=port) - uvicorn.run(self.app, host=host, port=port) - - -def create_app(api_key: Optional[str] = None, model: Optional[str] = None) -> FastAPI: - """ - 创建FastAPI应用(用于uvicorn启动) - - 使用方式: - uvicorn a2a_server:app --host 0.0.0.0 --port 8080 - - 或设置环境变量后: - export LITELLM_API_KEY="your-key" - export LITELLM_MODEL="your-model" - uvicorn a2a_server:app --host 0.0.0.0 --port 8080 - """ - import os - api_key = api_key or os.getenv("LITELLM_API_KEY") - model = model or os.getenv("LITELLM_MODEL") - - server = A2AAgentServer(api_key=api_key, model=model) - return server.app - - -# uvicorn 启动入口 -# 环境变量: LITELLM_API_KEY, LITELLM_MODEL -app = create_app() diff --git a/search_agent/a2a-ai-search-agent/agent.py b/search_agent/a2a-ai-search-agent/agent.py deleted file mode 100644 index f190c64..0000000 --- a/search_agent/a2a-ai-search-agent/agent.py +++ /dev/null @@ -1,333 +0,0 @@ -""" -LiteLLM Agent 核心模块 -基于LiteLLM框架的Agent实现,支持A2A协议 -""" - -import asyncio -import json -import uuid -from typing import AsyncGenerator, Optional, Dict, Any, List -from dataclasses import dataclass, field -from datetime import datetime - -import httpx -import structlog - -from config import LiteLLMConfig, AgentConfig, get_config - -# 配置日志 -logger = structlog.get_logger() - - -@dataclass -class Message: - """消息数据结构""" - role: str # user, assistant, system - content: str - message_id: str = field(default_factory=lambda: uuid.uuid4().hex) - timestamp: datetime = field(default_factory=datetime.now) - metadata: Dict[str, Any] = field(default_factory=dict) - - -@dataclass -class Conversation: - """对话上下文""" - conversation_id: str = field(default_factory=lambda: uuid.uuid4().hex) - messages: List[Message] = field(default_factory=list) - created_at: datetime = field(default_factory=datetime.now) - - def add_message(self, role: str, content: str) -> Message: - """添加消息到对话""" - msg = Message(role=role, content=content) - self.messages.append(msg) - return msg - - def to_openai_format(self) -> List[Dict[str, str]]: - """转换为OpenAI格式的消息列表""" - return [{"role": m.role, "content": m.content} for m in self.messages] - - -class LiteLLMAgent: - """ - 基于LiteLLM的Agent实现 - - 支持功能: - - 多轮对话 - - 流式响应 - - 工具调用 - - A2A协议兼容 - """ - - def __init__( - self, - api_key: Optional[str] = None, - model: Optional[str] = None, - litellm_config: Optional[LiteLLMConfig] = None, - agent_config: Optional[AgentConfig] = None - ): - """ - 初始化Agent - - Args: - api_key: LiteLLM API密钥 - model: 模型名称 - litellm_config: LiteLLM配置对象 - agent_config: Agent配置对象 - """ - if litellm_config: - self.llm_config = litellm_config - else: - self.llm_config = LiteLLMConfig(api_key=api_key, model=model) - - if agent_config: - self.agent_config = agent_config - else: - self.agent_config = AgentConfig() - - # 验证配置 - self.llm_config.validate() - - # HTTP客户端 - self._client: Optional[httpx.AsyncClient] = None - - # 对话管理 - self.conversations: Dict[str, Conversation] = {} - - # 工具注册 - self.tools: Dict[str, callable] = {} - - logger.info( - "Agent初始化完成", - agent_name=self.agent_config.name, - model=self.llm_config.model, - base_url=self.llm_config.base_url - ) - - async def _get_client(self) -> httpx.AsyncClient: - """获取或创建HTTP客户端""" - if self._client is None or self._client.is_closed: - self._client = httpx.AsyncClient( - timeout=httpx.Timeout(self.llm_config.timeout), - headers={ - "Authorization": f"Bearer {self.llm_config.api_key}", - "Content-Type": "application/json" - } - ) - return self._client - - async def close(self): - """关闭资源""" - if self._client and not self._client.is_closed: - await self._client.aclose() - - def register_tool(self, name: str, func: callable, description: str = ""): - """注册工具函数""" - self.tools[name] = { - "function": func, - "description": description - } - logger.info(f"注册工具: {name}") - - def get_or_create_conversation(self, conversation_id: Optional[str] = None) -> Conversation: - """获取或创建对话""" - if conversation_id and conversation_id in self.conversations: - return self.conversations[conversation_id] - - conv = Conversation(conversation_id=conversation_id or uuid.uuid4().hex) - # 添加系统提示 - conv.add_message("system", self.agent_config.system_prompt) - self.conversations[conv.conversation_id] = conv - return conv - - async def chat( - self, - message: str, - conversation_id: Optional[str] = None, - stream: bool = False - ) -> str | AsyncGenerator[str, None]: - """ - 发送消息并获取回复 - - Args: - message: 用户消息 - conversation_id: 对话ID(用于多轮对话) - stream: 是否流式响应 - - Returns: - 如果stream=False,返回完整回复字符串 - 如果stream=True,返回异步生成器 - """ - # 获取对话上下文 - conversation = self.get_or_create_conversation(conversation_id) - conversation.add_message("user", message) - - if stream: - return self._stream_chat(conversation) - else: - return await self._simple_chat(conversation) - - async def _simple_chat(self, conversation: Conversation) -> str: - """非流式对话""" - client = await self._get_client() - - request_body = { - "model": self.llm_config.model, - "messages": conversation.to_openai_format(), - "temperature": self.llm_config.temperature, - "max_tokens": self.llm_config.max_tokens - } - - logger.debug("发送请求", endpoint=self.llm_config.chat_endpoint) - - try: - response = await client.post( - self.llm_config.chat_endpoint, - json=request_body - ) - response.raise_for_status() - - result = response.json() - assistant_message = result["choices"][0]["message"]["content"] - - # 保存助手回复到对话 - conversation.add_message("assistant", assistant_message) - - logger.info("收到回复", length=len(assistant_message)) - return assistant_message - - except httpx.HTTPStatusError as e: - logger.error("HTTP错误", status_code=e.response.status_code, detail=e.response.text) - raise - except Exception as e: - logger.error("请求失败", error=str(e)) - raise - - async def _stream_chat(self, conversation: Conversation) -> AsyncGenerator[str, None]: - """流式对话""" - client = await self._get_client() - - request_body = { - "model": self.llm_config.model, - "messages": conversation.to_openai_format(), - "temperature": self.llm_config.temperature, - "max_tokens": self.llm_config.max_tokens, - "stream": True - } - - full_response = "" - - try: - async with client.stream( - "POST", - self.llm_config.chat_endpoint, - json=request_body - ) as response: - response.raise_for_status() - - async for line in response.aiter_lines(): - if line.startswith("data: "): - data = line[6:] - if data == "[DONE]": - break - - try: - chunk = json.loads(data) - delta = chunk.get("choices", [{}])[0].get("delta", {}) - content = delta.get("content", "") - if content: - full_response += content - yield content - except json.JSONDecodeError: - continue - - # 保存完整回复到对话 - conversation.add_message("assistant", full_response) - - except Exception as e: - logger.error("流式请求失败", error=str(e)) - raise - - async def invoke_tool(self, tool_name: str, **kwargs) -> Any: - """调用注册的工具""" - if tool_name not in self.tools: - raise ValueError(f"未找到工具: {tool_name}") - - tool = self.tools[tool_name] - func = tool["function"] - - logger.info(f"调用工具: {tool_name}", kwargs=kwargs) - - if asyncio.iscoroutinefunction(func): - return await func(**kwargs) - else: - return func(**kwargs) - - -# 示例工具函数 -def tool_get_current_time() -> str: - """获取当前时间""" - return datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - -def tool_calculate(expression: str) -> str: - """计算数学表达式""" - try: - # 安全的数学计算 - allowed_chars = set("0123456789+-*/.() ") - if not all(c in allowed_chars for c in expression): - return "错误: 不支持的字符" - result = eval(expression) - return str(result) - except Exception as e: - return f"计算错误: {str(e)}" - - -async def main(): - """示例用法""" - import os - - # 从环境变量或命令行获取配置 - api_key = os.getenv("LITELLM_API_KEY") - model = os.getenv("LITELLM_MODEL", "gpt-4") - - if not api_key: - print("请设置 LITELLM_API_KEY 环境变量") - print("示例: export LITELLM_API_KEY='your-api-key'") - return - - # 创建Agent - agent = LiteLLMAgent(api_key=api_key, model=model) - - # 注册工具 - agent.register_tool("get_time", tool_get_current_time, "获取当前时间") - agent.register_tool("calculate", tool_calculate, "计算数学表达式") - - try: - # 简单对话示例 - print("\n=== 简单对话 ===") - response = await agent.chat("你好,请介绍一下你自己") - print(f"Agent: {response}") - - # 多轮对话示例 - print("\n=== 多轮对话 ===") - conv_id = "test-conversation" - response1 = await agent.chat("我的名字叫张三", conversation_id=conv_id) - print(f"Agent: {response1}") - - response2 = await agent.chat("你还记得我的名字吗?", conversation_id=conv_id) - print(f"Agent: {response2}") - - # 流式响应示例 - print("\n=== 流式响应 ===") - print("Agent: ", end="", flush=True) - stream = await agent.chat("请用3句话描述Python编程语言", stream=True) - async for chunk in stream: - print(chunk, end="", flush=True) - print() - - finally: - await agent.close() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/search_agent/a2a-ai-search-agent/config.py b/search_agent/a2a-ai-search-agent/config.py deleted file mode 100644 index 44f5241..0000000 --- a/search_agent/a2a-ai-search-agent/config.py +++ /dev/null @@ -1,141 +0,0 @@ -""" -LiteLLM Agent 配置模块 -支持用户传入密钥和模型名称 -""" - -import os -from dataclasses import dataclass, field -from typing import Optional -from dotenv import load_dotenv - -# 加载环境变量 -load_dotenv() - - -@dataclass -class LiteLLMConfig: - """LiteLLM 配置""" - # 基础URL - 用户提供的LiteLLM服务地址 - base_url: str = "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io" - - # 完整的chat completions端点 - chat_endpoint: str = field(init=False) - - # API密钥 - 需要用户传入 - api_key: Optional[str] = None - - # 模型名称 - 需要用户传入 - model: Optional[str] = None - - # 请求超时时间(秒) - timeout: int = 120 - - # 最大重试次数 - max_retries: int = 3 - - # 温度参数 - temperature: float = 0.7 - - # 最大token数 - max_tokens: int = 4096 - - def __post_init__(self): - self.chat_endpoint = f"{self.base_url}/chat/completions" - - # 从环境变量读取(如果未直接提供) - if self.api_key is None: - self.api_key = os.getenv("LITELLM_API_KEY") - if self.model is None: - self.model = os.getenv("LITELLM_MODEL", "gpt-4") - - def validate(self) -> bool: - """验证配置是否完整""" - if not self.api_key: - raise ValueError("API密钥未设置! 请设置 LITELLM_API_KEY 环境变量或直接传入 api_key") - if not self.model: - raise ValueError("模型名称未设置! 请设置 LITELLM_MODEL 环境变量或直接传入 model") - return True - - -@dataclass -class AgentConfig: - """Agent 配置""" - # Agent名称 - name: str = "xiaohei-agent" - - # Agent描述 - description: str = "一个基于LiteLLM的智能Agent,支持A2A协议" - - # Agent版本 - version: str = "1.0.0" - - # 服务端口 - port: int = 8080 - - # 服务主机 - host: str = "0.0.0.0" - - # 是否启用流式响应 - enable_streaming: bool = True - - # 系统提示词 - system_prompt: str = """你是小黑Agent,一个智能助手。 -你可以帮助用户完成各种任务,包括: -- 回答问题 -- 代码编写和解释 -- 文档分析 -- 任务规划 - -请用中文回答用户的问题,保持友好和专业。""" - - -@dataclass -class A2AConfig: - """A2A协议配置""" - # A2A协议版本 - protocol_version: str = "1.0" - - # Agent Card配置 - agent_card: dict = field(default_factory=lambda: { - "name": "xiaohei-agent", - "description": "基于LiteLLM的智能Agent,支持A2A协议通信", - "version": "1.0.0", - "capabilities": { - "text": True, - "streaming": True, - "push_notifications": False - }, - "skills": [ - { - "id": "general-assistant", - "name": "通用助手", - "description": "回答问题、提供建议、协助任务" - }, - { - "id": "code-helper", - "name": "代码助手", - "description": "编写、解释和调试代码" - } - ] - }) - - -def get_config( - api_key: Optional[str] = None, - model: Optional[str] = None -) -> tuple[LiteLLMConfig, AgentConfig, A2AConfig]: - """ - 获取完整配置 - - Args: - api_key: LiteLLM API密钥(可选,也可通过环境变量设置) - model: 模型名称(可选,也可通过环境变量设置) - - Returns: - (LiteLLMConfig, AgentConfig, A2AConfig) 配置元组 - """ - litellm_config = LiteLLMConfig(api_key=api_key, model=model) - agent_config = AgentConfig() - a2a_config = A2AConfig() - - return litellm_config, agent_config, a2a_config diff --git a/search_agent/a2a-ai-search-agent/deploy.sh b/search_agent/a2a-ai-search-agent/deploy.sh deleted file mode 100755 index effb8d2..0000000 --- a/search_agent/a2a-ai-search-agent/deploy.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# 部署小黑Agent到AKS - -echo "开始部署小黑Agent到AKS..." - -# 应用ConfigMap -kubectl apply -f k8s/configmap.yaml - -# 应用Secret -kubectl apply -f k8s/secret.yaml - -# 应用Deployment -kubectl apply -f k8s/deployment.yaml - -# 应用Service -kubectl apply -f k8s/service.yaml - -echo "部署完成!" -echo "" -echo "查看Pod状态:" -echo " kubectl get pods -l app=xiaohei-agent" -echo "" -echo "查看Service状态:" -echo " kubectl get svc xiaohei-agent-service" -echo "" -echo "查看外网IP (可能需要几分钟):" -echo " kubectl get svc xiaohei-agent-service -w" diff --git a/search_agent/a2a-ai-search-agent/k8s/configmap.yaml b/search_agent/a2a-ai-search-agent/k8s/configmap.yaml deleted file mode 100644 index 3274ec3..0000000 --- a/search_agent/a2a-ai-search-agent/k8s/configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# 小黑Agent ConfigMap -# 存储非敏感配置 - -apiVersion: v1 -kind: ConfigMap -metadata: - name: xiaohei-config - labels: - app: xiaohei-agent -data: - litellm-model: "taiji/gpt-4o-mini" diff --git a/search_agent/a2a-ai-search-agent/k8s/deployment.yaml b/search_agent/a2a-ai-search-agent/k8s/deployment.yaml deleted file mode 100644 index df251d1..0000000 --- a/search_agent/a2a-ai-search-agent/k8s/deployment.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# 小黑Agent Kubernetes 部署配置 -# 适用于 AKS (Azure Kubernetes Service) - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: xiaohei-agent - labels: - app: xiaohei-agent - version: v1 -spec: - replicas: 2 - selector: - matchLabels: - app: xiaohei-agent - template: - metadata: - labels: - app: xiaohei-agent - version: v1 - spec: - containers: - - name: xiaohei-agent - image: agnettaiji.azurecr.io/xiaohei-agent:latest - imagePullPolicy: Always - ports: - - containerPort: 9000 - protocol: TCP - env: - - name: LITELLM_API_KEY - valueFrom: - secretKeyRef: - name: xiaohei-secrets - key: litellm-api-key - - name: LITELLM_MODEL - valueFrom: - configMapKeyRef: - name: xiaohei-config - key: litellm-model - resources: - requests: - cpu: "100m" - memory: "256Mi" - limits: - cpu: "500m" - memory: "512Mi" - # 存活探针 - livenessProbe: - httpGet: - path: /health - port: 9000 - initialDelaySeconds: 10 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 - # 就绪探针 - readinessProbe: - httpGet: - path: /health - port: 9000 - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 - # 安全上下文 - securityContext: - runAsNonRoot: true - runAsUser: 1000 - # 优雅终止 - terminationGracePeriodSeconds: 30 diff --git a/search_agent/a2a-ai-search-agent/k8s/secret.yaml b/search_agent/a2a-ai-search-agent/k8s/secret.yaml deleted file mode 100644 index 9adfcaf..0000000 --- a/search_agent/a2a-ai-search-agent/k8s/secret.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 小黑Agent Secret -# 存储敏感配置(API密钥等) -# 注意: 实际部署时请使用 Azure Key Vault 或 kubectl create secret - -apiVersion: v1 -kind: Secret -metadata: - name: xiaohei-secrets - labels: - app: xiaohei-agent -type: Opaque -stringData: - litellm-api-key: "sk-7tvnni908QJzuuUWedvx5w" diff --git a/search_agent/a2a-ai-search-agent/k8s/service.yaml b/search_agent/a2a-ai-search-agent/k8s/service.yaml deleted file mode 100644 index 5ce814d..0000000 --- a/search_agent/a2a-ai-search-agent/k8s/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# 小黑Agent Kubernetes Service -# 暴露服务供外部访问 - -apiVersion: v1 -kind: Service -metadata: - name: xiaohei-agent-service - labels: - app: xiaohei-agent -spec: - type: LoadBalancer # 使用Azure LoadBalancer暴露外网 - selector: - app: xiaohei-agent - ports: - - name: http - port: 80 - targetPort: 9000 - protocol: TCP diff --git a/search_agent/a2a-ai-search-agent/requirements.txt b/search_agent/a2a-ai-search-agent/requirements.txt deleted file mode 100644 index 8f44ea1..0000000 --- a/search_agent/a2a-ai-search-agent/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# LiteLLM Agent API服务依赖 -httpx>=0.27.0 -fastapi>=0.115.0 -uvicorn[standard]>=0.32.0 -pydantic>=2.0.0 -python-dotenv>=1.0.0 -structlog>=24.0.0