client.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. """
  2. Gateway Client SDK
  3. 通用的 Gateway 客户端实现,提供:
  4. - Agent 注册和连接管理
  5. - 消息发送和接收
  6. - 在线状态查询
  7. - Agent 列表查询
  8. 不依赖任何特定的 Agent 框架。
  9. """
  10. import asyncio
  11. import json
  12. import logging
  13. from dataclasses import dataclass, field
  14. from datetime import datetime
  15. from typing import Any, Callable, Dict, List, Optional
  16. from urllib.parse import urlencode
  17. import httpx
  18. logger = logging.getLogger(__name__)
  19. @dataclass
  20. class Message:
  21. """MAMP 协议消息"""
  22. conversation_id: str
  23. content: List[Dict[str, Any]] # 多模态内容
  24. metadata: Dict[str, Any] = field(default_factory=dict)
  25. message_id: Optional[str] = None
  26. timestamp: Optional[str] = None
  27. @dataclass
  28. class AgentCard:
  29. """Agent 身份卡片"""
  30. agent_id: str
  31. agent_name: str
  32. agent_type: Optional[str] = None
  33. capabilities: List[str] = field(default_factory=list)
  34. description: Optional[str] = None
  35. version: Optional[str] = None
  36. metadata: Dict[str, Any] = field(default_factory=dict)
  37. class GatewayClient:
  38. """
  39. Gateway 客户端
  40. 使用示例:
  41. ```python
  42. client = GatewayClient(
  43. gateway_url="http://localhost:8001",
  44. agent_card=AgentCard(
  45. agent_id="my-agent-001",
  46. agent_name="My Agent",
  47. capabilities=["search", "analyze"]
  48. )
  49. )
  50. # 发送消息
  51. await client.send_message(
  52. to_agent_id="target-agent",
  53. content=[{"type": "text", "text": "Hello"}],
  54. conversation_id="conv-123"
  55. )
  56. # 查询在线 Agent
  57. agents = await client.list_agents()
  58. ```
  59. """
  60. def __init__(
  61. self,
  62. gateway_url: str,
  63. agent_card: AgentCard,
  64. on_message: Optional[Callable[[Message], None]] = None,
  65. auto_reconnect: bool = True,
  66. heartbeat_interval: int = 30,
  67. ):
  68. """
  69. 初始化 Gateway 客户端
  70. Args:
  71. gateway_url: Gateway HTTP API 地址(如 http://localhost:8001)
  72. agent_card: Agent 身份信息
  73. on_message: 收到消息时的回调函数
  74. auto_reconnect: 是否自动重连
  75. heartbeat_interval: 心跳间隔(秒)
  76. """
  77. self.gateway_url = gateway_url.rstrip("/")
  78. self.agent_card = agent_card
  79. self.on_message = on_message
  80. self.auto_reconnect = auto_reconnect
  81. self.heartbeat_interval = heartbeat_interval
  82. self._http_client: Optional[httpx.AsyncClient] = None
  83. self._connected = False
  84. async def __aenter__(self):
  85. """异步上下文管理器入口"""
  86. await self.connect()
  87. return self
  88. async def __aexit__(self, exc_type, exc_val, exc_tb):
  89. """异步上下文管理器出口"""
  90. await self.disconnect()
  91. async def connect(self):
  92. """连接到 Gateway"""
  93. if self._connected:
  94. return
  95. self._http_client = httpx.AsyncClient(timeout=30.0)
  96. # 注册 Agent
  97. try:
  98. response = await self._http_client.post(
  99. f"{self.gateway_url}/gateway/register",
  100. json={
  101. "agent_id": self.agent_card.agent_id,
  102. "agent_name": self.agent_card.agent_name,
  103. "agent_type": self.agent_card.agent_type,
  104. "capabilities": self.agent_card.capabilities,
  105. "description": self.agent_card.description,
  106. "version": self.agent_card.version,
  107. "metadata": self.agent_card.metadata,
  108. }
  109. )
  110. response.raise_for_status()
  111. self._connected = True
  112. logger.info(f"Connected to Gateway: {self.agent_card.agent_id}")
  113. except Exception as e:
  114. logger.error(f"Failed to connect to Gateway: {e}")
  115. raise
  116. async def disconnect(self):
  117. """断开连接"""
  118. if not self._connected:
  119. return
  120. try:
  121. if self._http_client:
  122. await self._http_client.post(
  123. f"{self.gateway_url}/gateway/unregister",
  124. json={"agent_id": self.agent_card.agent_id}
  125. )
  126. await self._http_client.aclose()
  127. self._http_client = None
  128. self._connected = False
  129. logger.info(f"Disconnected from Gateway: {self.agent_card.agent_id}")
  130. except Exception as e:
  131. logger.error(f"Error during disconnect: {e}")
  132. async def send_message(
  133. self,
  134. to_agent_id: str,
  135. content: List[Dict[str, Any]],
  136. conversation_id: str,
  137. metadata: Optional[Dict[str, Any]] = None
  138. ) -> Dict[str, Any]:
  139. """
  140. 发送消息到其他 Agent
  141. Args:
  142. to_agent_id: 目标 Agent ID
  143. content: 消息内容(MAMP 格式)
  144. conversation_id: 对话 ID
  145. metadata: 元数据
  146. Returns:
  147. 发送结果
  148. """
  149. if not self._connected:
  150. raise RuntimeError("Not connected to Gateway")
  151. response = await self._http_client.post(
  152. f"{self.gateway_url}/gateway/send",
  153. json={
  154. "from_agent_id": self.agent_card.agent_id,
  155. "to_agent_id": to_agent_id,
  156. "conversation_id": conversation_id,
  157. "content": content,
  158. "metadata": metadata or {}
  159. }
  160. )
  161. response.raise_for_status()
  162. return response.json()
  163. async def list_agents(
  164. self,
  165. online_only: bool = True,
  166. agent_type: Optional[str] = None
  167. ) -> List[Dict[str, Any]]:
  168. """
  169. 查询 Agent 列表
  170. Args:
  171. online_only: 只返回在线 Agent
  172. agent_type: 过滤 Agent 类型
  173. Returns:
  174. Agent 列表
  175. """
  176. if not self._connected:
  177. raise RuntimeError("Not connected to Gateway")
  178. params = {"online_only": online_only}
  179. if agent_type:
  180. params["agent_type"] = agent_type
  181. response = await self._http_client.get(
  182. f"{self.gateway_url}/gateway/agents",
  183. params=params
  184. )
  185. response.raise_for_status()
  186. data = response.json()
  187. return data.get("agents", [])
  188. async def get_agent_status(self, agent_id: str) -> Dict[str, Any]:
  189. """
  190. 查询指定 Agent 的状态
  191. Args:
  192. agent_id: Agent ID
  193. Returns:
  194. Agent 状态信息
  195. """
  196. if not self._connected:
  197. raise RuntimeError("Not connected to Gateway")
  198. from urllib.parse import quote
  199. encoded_id = quote(agent_id, safe='')
  200. response = await self._http_client.get(
  201. f"{self.gateway_url}/gateway/status/{encoded_id}"
  202. )
  203. response.raise_for_status()
  204. return response.json()
  205. async def heartbeat(self):
  206. """发送心跳"""
  207. if not self._connected:
  208. return
  209. try:
  210. response = await self._http_client.post(
  211. f"{self.gateway_url}/gateway/heartbeat",
  212. json={"agent_id": self.agent_card.agent_id}
  213. )
  214. response.raise_for_status()
  215. except Exception as e:
  216. logger.error(f"Heartbeat failed: {e}")
  217. if self.auto_reconnect:
  218. await self.connect()