Initial commit to git.yoin
This commit is contained in:
60
uni-agent/adapters/__init__.py
Normal file
60
uni-agent/adapters/__init__.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
UniAgent 协议适配器
|
||||
|
||||
支持的协议:
|
||||
- ANP: Agent Network Protocol (去中心化身份 + Agent 网络)
|
||||
- MCP: Model Context Protocol (LLM 工具调用)
|
||||
- A2A: Agent-to-Agent (Google Agent 间协作)
|
||||
- AITP: Agent Interaction & Transaction Protocol (交互 + 交易)
|
||||
- Agent Protocol: 统一 REST API
|
||||
- LMOS: Language Model OS (企业级 Agent 平台)
|
||||
"""
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
from .anp import ANPAdapter
|
||||
from .mcp import MCPAdapter
|
||||
from .a2a import A2AAdapter
|
||||
from .aitp import AITPAdapter
|
||||
from .agent_protocol import AgentProtocolAdapter
|
||||
from .lmos import LMOSAdapter
|
||||
|
||||
ADAPTERS = {
|
||||
"anp": ANPAdapter,
|
||||
"mcp": MCPAdapter,
|
||||
"a2a": A2AAdapter,
|
||||
"aitp": AITPAdapter,
|
||||
"agent_protocol": AgentProtocolAdapter,
|
||||
"ap": AgentProtocolAdapter,
|
||||
"lmos": LMOSAdapter,
|
||||
}
|
||||
|
||||
def get_adapter(protocol: str) -> ProtocolAdapter:
|
||||
"""获取协议适配器"""
|
||||
adapter_class = ADAPTERS.get(protocol)
|
||||
if not adapter_class:
|
||||
raise ValueError(f"不支持的协议: {protocol},可用协议: {list(ADAPTERS.keys())}")
|
||||
return adapter_class()
|
||||
|
||||
def register_adapter(protocol: str, adapter_class: type):
|
||||
"""注册新的协议适配器"""
|
||||
ADAPTERS[protocol] = adapter_class
|
||||
|
||||
def list_protocols() -> list:
|
||||
"""列出所有支持的协议"""
|
||||
return list(set(ADAPTERS.keys()))
|
||||
|
||||
__all__ = [
|
||||
"ProtocolAdapter",
|
||||
"Connection",
|
||||
"AgentInfo",
|
||||
"ANPAdapter",
|
||||
"MCPAdapter",
|
||||
"A2AAdapter",
|
||||
"AITPAdapter",
|
||||
"AgentProtocolAdapter",
|
||||
"LMOSAdapter",
|
||||
"get_adapter",
|
||||
"register_adapter",
|
||||
"list_protocols",
|
||||
"ADAPTERS",
|
||||
]
|
||||
225
uni-agent/adapters/a2a.py
Normal file
225
uni-agent/adapters/a2a.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
A2A (Agent-to-Agent) 适配器
|
||||
Google 提出的 Agent 间协作协议
|
||||
|
||||
参考: https://github.com/google/a2a
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class A2AAdapter(ProtocolAdapter):
|
||||
"""A2A 协议适配器"""
|
||||
|
||||
protocol_name = "a2a"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._agent_cards: Dict[str, dict] = {}
|
||||
|
||||
async def _fetch_agent_card(self, endpoint: str) -> dict:
|
||||
"""获取 Agent Card"""
|
||||
if endpoint in self._agent_cards:
|
||||
return self._agent_cards[endpoint]
|
||||
|
||||
agent_json_url = endpoint.rstrip("/")
|
||||
if not agent_json_url.endswith("agent.json"):
|
||||
agent_json_url = f"{agent_json_url}/.well-known/agent.json"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(agent_json_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
card = await resp.json()
|
||||
self._agent_cards[endpoint] = card
|
||||
return card
|
||||
raise Exception(f"获取 Agent Card 失败: HTTP {resp.status}")
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
if not endpoint:
|
||||
raise ValueError("A2A Agent 配置必须包含 endpoint")
|
||||
|
||||
agent_card = await self._fetch_agent_card(endpoint)
|
||||
|
||||
rpc_url = None
|
||||
if "url" in agent_card:
|
||||
rpc_url = agent_card["url"]
|
||||
elif "capabilities" in agent_card:
|
||||
caps = agent_card.get("capabilities", {})
|
||||
if "streaming" in caps:
|
||||
rpc_url = caps.get("streaming", {}).get("streamingUrl")
|
||||
|
||||
if not rpc_url:
|
||||
rpc_url = endpoint.rstrip("/") + "/rpc"
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=rpc_url,
|
||||
session=None,
|
||||
metadata={
|
||||
"agent_card": agent_card,
|
||||
"original_endpoint": endpoint,
|
||||
"auth": agent_config.get("auth", {}),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 A2A Agent 方法"""
|
||||
rpc_url = connection.endpoint
|
||||
auth_config = connection.metadata.get("auth", {})
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if auth_config.get("type") == "api_key":
|
||||
headers["Authorization"] = f"Bearer {auth_config.get('api_key', '')}"
|
||||
elif auth_config.get("type") == "oauth2":
|
||||
token = await self._get_oauth_token(auth_config)
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
|
||||
if method == "tasks/send":
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": task_id,
|
||||
"method": "tasks/send",
|
||||
"params": {
|
||||
"id": task_id,
|
||||
"message": params.get("message", {}),
|
||||
}
|
||||
}
|
||||
elif method == "tasks/get":
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": task_id,
|
||||
"method": "tasks/get",
|
||||
"params": {
|
||||
"id": params.get("task_id", task_id),
|
||||
}
|
||||
}
|
||||
else:
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": task_id,
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
rpc_url,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {
|
||||
"success": True,
|
||||
"result": result.get("result", result),
|
||||
"task_id": task_id,
|
||||
}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"HTTP {resp.status}: {error_text}",
|
||||
}
|
||||
|
||||
async def _get_oauth_token(self, auth_config: dict) -> str:
|
||||
"""获取 OAuth2 令牌"""
|
||||
token_url = auth_config.get("token_url")
|
||||
client_id = auth_config.get("client_id")
|
||||
client_secret = auth_config.get("client_secret")
|
||||
|
||||
if not all([token_url, client_id, client_secret]):
|
||||
raise ValueError("OAuth2 配置不完整")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
token_url,
|
||||
data={
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
}
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return result.get("access_token", "")
|
||||
raise Exception(f"获取 OAuth2 令牌失败: HTTP {resp.status}")
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "a2a":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@a2a",
|
||||
protocol="a2a",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取 Agent 支持的方法(从 Agent Card 的 skills)"""
|
||||
agent_card = connection.metadata.get("agent_card", {})
|
||||
skills = agent_card.get("skills", [])
|
||||
|
||||
methods = []
|
||||
for skill in skills:
|
||||
methods.append({
|
||||
"name": skill.get("id", skill.get("name", "unknown")),
|
||||
"description": skill.get("description", ""),
|
||||
"inputSchema": skill.get("inputSchema", {}),
|
||||
"outputSchema": skill.get("outputSchema", {}),
|
||||
})
|
||||
|
||||
methods.extend([
|
||||
{"name": "tasks/send", "description": "发送任务消息"},
|
||||
{"name": "tasks/get", "description": "获取任务状态"},
|
||||
{"name": "tasks/cancel", "description": "取消任务"},
|
||||
])
|
||||
|
||||
return methods
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config
|
||||
211
uni-agent/adapters/agent_protocol.py
Normal file
211
uni-agent/adapters/agent_protocol.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Agent Protocol 适配器
|
||||
AI Engineer Foundation 提出的 Agent 统一 REST API
|
||||
|
||||
参考: https://agentprotocol.ai
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class AgentProtocolAdapter(ProtocolAdapter):
|
||||
"""Agent Protocol 适配器"""
|
||||
|
||||
protocol_name = "agent_protocol"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._tasks: Dict[str, dict] = {}
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
if not endpoint:
|
||||
raise ValueError("Agent Protocol 配置必须包含 endpoint")
|
||||
|
||||
endpoint = endpoint.rstrip("/")
|
||||
if not endpoint.endswith("/ap/v1"):
|
||||
endpoint = f"{endpoint}/ap/v1"
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=None,
|
||||
metadata=agent_config
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 Agent Protocol API"""
|
||||
endpoint = connection.endpoint
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
api_key = connection.metadata.get("api_key")
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
|
||||
if method == "create_task":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/agent/tasks",
|
||||
json={"input": params.get("input", "")},
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status in [200, 201]:
|
||||
result = await resp.json()
|
||||
task_id = result.get("task_id")
|
||||
self._tasks[task_id] = result
|
||||
return {"success": True, "result": result, "task_id": task_id}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "execute_step":
|
||||
task_id = params.get("task_id")
|
||||
if not task_id:
|
||||
return {"success": False, "error": "缺少 task_id"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/agent/tasks/{task_id}/steps",
|
||||
json={"input": params.get("input", "")},
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status in [200, 201]:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "get_task":
|
||||
task_id = params.get("task_id")
|
||||
if not task_id:
|
||||
return {"success": False, "error": "缺少 task_id"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/agent/tasks/{task_id}",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "list_tasks":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/agent/tasks",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "get_artifacts":
|
||||
task_id = params.get("task_id")
|
||||
if not task_id:
|
||||
return {"success": False, "error": "缺少 task_id"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/agent/tasks/{task_id}/artifacts",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
else:
|
||||
return {"success": False, "error": f"未知方法: {method}"}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "agent_protocol":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@agent_protocol",
|
||||
protocol="agent_protocol",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取支持的方法"""
|
||||
return [
|
||||
{
|
||||
"name": "create_task",
|
||||
"description": "创建新任务",
|
||||
"inputSchema": {"input": "string"},
|
||||
},
|
||||
{
|
||||
"name": "execute_step",
|
||||
"description": "执行任务步骤",
|
||||
"inputSchema": {"task_id": "string", "input": "string"},
|
||||
},
|
||||
{
|
||||
"name": "get_task",
|
||||
"description": "获取任务状态",
|
||||
"inputSchema": {"task_id": "string"},
|
||||
},
|
||||
{
|
||||
"name": "list_tasks",
|
||||
"description": "列出所有任务",
|
||||
"inputSchema": {},
|
||||
},
|
||||
{
|
||||
"name": "get_artifacts",
|
||||
"description": "获取任务产物",
|
||||
"inputSchema": {"task_id": "string"},
|
||||
},
|
||||
]
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config
|
||||
217
uni-agent/adapters/aitp.py
Normal file
217
uni-agent/adapters/aitp.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
AITP (Agent Interaction & Transaction Protocol) 适配器
|
||||
NEAR 基金会提出的 Agent 交互与交易协议
|
||||
|
||||
参考: https://aitp.dev
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class AITPAdapter(ProtocolAdapter):
|
||||
"""AITP 协议适配器"""
|
||||
|
||||
protocol_name = "aitp"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._threads: Dict[str, dict] = {}
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接 - 创建 Thread"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
if not endpoint:
|
||||
raise ValueError("AITP Agent 配置必须包含 endpoint")
|
||||
|
||||
thread_id = str(uuid.uuid4())
|
||||
|
||||
self._threads[thread_id] = {
|
||||
"id": thread_id,
|
||||
"messages": [],
|
||||
"status": "open",
|
||||
}
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=thread_id,
|
||||
metadata={
|
||||
"thread_id": thread_id,
|
||||
"wallet": agent_config.get("wallet", {}),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 AITP Agent"""
|
||||
endpoint = connection.endpoint
|
||||
thread_id = connection.session
|
||||
wallet_config = connection.metadata.get("wallet", {})
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if method == "message":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"message": {
|
||||
"role": "user",
|
||||
"content": params.get("content", ""),
|
||||
"parts": params.get("parts", []),
|
||||
}
|
||||
}
|
||||
elif method == "payment":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"capability": "aitp-01",
|
||||
"payment_request": {
|
||||
"amount": params.get("amount"),
|
||||
"currency": params.get("currency", "NEAR"),
|
||||
"recipient": params.get("recipient"),
|
||||
"memo": params.get("memo", ""),
|
||||
}
|
||||
}
|
||||
|
||||
if wallet_config.get("type") == "near":
|
||||
payload["wallet"] = {
|
||||
"type": "near",
|
||||
"account_id": wallet_config.get("account_id"),
|
||||
}
|
||||
elif method == "decision":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"capability": "aitp-02",
|
||||
"decision_request": {
|
||||
"question": params.get("question"),
|
||||
"options": params.get("options", []),
|
||||
"allow_custom": params.get("allow_custom", False),
|
||||
}
|
||||
}
|
||||
elif method == "data_request":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"capability": "aitp-03",
|
||||
"data_request": {
|
||||
"schema": params.get("schema", {}),
|
||||
"description": params.get("description", ""),
|
||||
}
|
||||
}
|
||||
else:
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/threads/{thread_id}/messages",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
|
||||
if thread_id in self._threads:
|
||||
self._threads[thread_id]["messages"].append(payload)
|
||||
self._threads[thread_id]["messages"].append(result)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"result": result,
|
||||
"thread_id": thread_id,
|
||||
}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"HTTP {resp.status}: {error_text}",
|
||||
}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "aitp":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@aitp",
|
||||
protocol="aitp",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接 - 关闭 Thread"""
|
||||
thread_id = connection.session
|
||||
if thread_id in self._threads:
|
||||
self._threads[thread_id]["status"] = "closed"
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取支持的方法(AITP 能力)"""
|
||||
return [
|
||||
{
|
||||
"name": "message",
|
||||
"description": "发送对话消息",
|
||||
"inputSchema": {"content": "string"},
|
||||
},
|
||||
{
|
||||
"name": "payment",
|
||||
"description": "AITP-01: 发起支付请求",
|
||||
"inputSchema": {
|
||||
"amount": "number",
|
||||
"currency": "string",
|
||||
"recipient": "string",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "decision",
|
||||
"description": "AITP-02: 请求用户决策",
|
||||
"inputSchema": {
|
||||
"question": "string",
|
||||
"options": "array",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "data_request",
|
||||
"description": "AITP-03: 请求结构化数据",
|
||||
"inputSchema": {
|
||||
"schema": "object",
|
||||
"description": "string",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config
|
||||
191
uni-agent/adapters/anp.py
Normal file
191
uni-agent/adapters/anp.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
ANP (Agent Network Protocol) 适配器
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
try:
|
||||
from anp.anp_crawler import ANPCrawler
|
||||
HAS_ANP = True
|
||||
except ImportError:
|
||||
HAS_ANP = False
|
||||
|
||||
|
||||
class ANPAdapter(ProtocolAdapter):
|
||||
"""ANP 协议适配器"""
|
||||
|
||||
protocol_name = "anp"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._crawler = None
|
||||
self._ad_cache: Dict[str, dict] = {}
|
||||
self._endpoint_cache: Dict[str, str] = {}
|
||||
|
||||
def _get_crawler(self) -> "ANPCrawler":
|
||||
"""获取 ANP Crawler 实例"""
|
||||
if not HAS_ANP:
|
||||
raise ImportError("请安装 anp 库: pip install anp")
|
||||
|
||||
if self._crawler is None:
|
||||
did_path = self.config_dir / "did.json"
|
||||
key_path = self.config_dir / "private-key.pem"
|
||||
|
||||
if did_path.exists() and key_path.exists():
|
||||
self._crawler = ANPCrawler(
|
||||
did_document_path=str(did_path),
|
||||
private_key_path=str(key_path)
|
||||
)
|
||||
else:
|
||||
raise FileNotFoundError(
|
||||
f"DID 配置文件不存在: {did_path} 或 {key_path}\n"
|
||||
"请运行 setup.sh 生成本地身份"
|
||||
)
|
||||
|
||||
return self._crawler
|
||||
|
||||
async def _fetch_ad(self, ad_url: str) -> dict:
|
||||
"""获取 Agent Description 文档"""
|
||||
if ad_url in self._ad_cache:
|
||||
return self._ad_cache[ad_url]
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(ad_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
ad = await resp.json()
|
||||
self._ad_cache[ad_url] = ad
|
||||
return ad
|
||||
raise Exception(f"获取 AD 失败: HTTP {resp.status}")
|
||||
|
||||
async def _get_endpoint(self, ad_url: str) -> str:
|
||||
"""从 AD 获取 RPC 端点"""
|
||||
if ad_url in self._endpoint_cache:
|
||||
return self._endpoint_cache[ad_url]
|
||||
|
||||
ad = await self._fetch_ad(ad_url)
|
||||
interfaces = ad.get("interfaces", [])
|
||||
|
||||
if not interfaces:
|
||||
raise ValueError(f"AD 中没有定义接口: {ad_url}")
|
||||
|
||||
interface_url = interfaces[0].get("url")
|
||||
if not interface_url:
|
||||
raise ValueError(f"接口 URL 为空: {ad_url}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(interface_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
interface_doc = await resp.json()
|
||||
servers = interface_doc.get("servers", [])
|
||||
if servers:
|
||||
endpoint = servers[0].get("url")
|
||||
self._endpoint_cache[ad_url] = endpoint
|
||||
return endpoint
|
||||
|
||||
raise ValueError(f"无法获取 RPC 端点: {ad_url}")
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
ad_url = agent_config.get("ad_url")
|
||||
if not ad_url:
|
||||
raise ValueError("ANP Agent 配置必须包含 ad_url")
|
||||
|
||||
ad = await self._fetch_ad(ad_url)
|
||||
endpoint = await self._get_endpoint(ad_url)
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=self._get_crawler(),
|
||||
metadata={
|
||||
"ad_url": ad_url,
|
||||
"ad": ad,
|
||||
"name": ad.get("name", ""),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 Agent 方法"""
|
||||
crawler = connection.session
|
||||
endpoint = connection.endpoint
|
||||
|
||||
result = await crawler.execute_json_rpc(
|
||||
endpoint=endpoint,
|
||||
method=method,
|
||||
params=params
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent(从本地配置)"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "anp":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@anp",
|
||||
protocol="anp",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("ad_url", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取 Agent 支持的方法"""
|
||||
ad_url = connection.metadata.get("ad_url")
|
||||
if not ad_url:
|
||||
return []
|
||||
|
||||
ad = await self._fetch_ad(ad_url)
|
||||
interfaces = ad.get("interfaces", [])
|
||||
|
||||
if not interfaces:
|
||||
return []
|
||||
|
||||
interface_url = interfaces[0].get("url")
|
||||
if not interface_url:
|
||||
return []
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(interface_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
interface_doc = await resp.json()
|
||||
return interface_doc.get("methods", [])
|
||||
|
||||
return []
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "ad_url" in agent_config
|
||||
120
uni-agent/adapters/base.py
Normal file
120
uni-agent/adapters/base.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
协议适配器基类
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentInfo:
|
||||
"""Agent 信息"""
|
||||
id: str
|
||||
protocol: str
|
||||
name: str = ""
|
||||
description: str = ""
|
||||
methods: List[str] = field(default_factory=list)
|
||||
endpoint: str = ""
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connection:
|
||||
"""连接对象"""
|
||||
agent_id: str
|
||||
protocol: str
|
||||
endpoint: str = ""
|
||||
session: Any = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def is_active(self) -> bool:
|
||||
return self.session is not None
|
||||
|
||||
|
||||
class ProtocolAdapter(ABC):
|
||||
"""协议适配器基类"""
|
||||
|
||||
protocol_name: str = "base"
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""
|
||||
建立与 Agent 的连接
|
||||
|
||||
Args:
|
||||
agent_config: Agent 配置信息
|
||||
|
||||
Returns:
|
||||
Connection 对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""
|
||||
调用 Agent 方法
|
||||
|
||||
Args:
|
||||
connection: 连接对象
|
||||
method: 方法名
|
||||
params: 参数
|
||||
timeout: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
调用结果
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""
|
||||
发现 Agent
|
||||
|
||||
Args:
|
||||
capability: 能力关键词(可选)
|
||||
|
||||
Returns:
|
||||
Agent 信息列表
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def close(self, connection: Connection):
|
||||
"""
|
||||
关闭连接
|
||||
|
||||
Args:
|
||||
connection: 连接对象
|
||||
"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""
|
||||
获取 Agent 支持的方法列表
|
||||
|
||||
Args:
|
||||
connection: 连接对象
|
||||
|
||||
Returns:
|
||||
方法列表
|
||||
"""
|
||||
return []
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""
|
||||
验证 Agent 配置
|
||||
|
||||
Args:
|
||||
agent_config: Agent 配置
|
||||
|
||||
Returns:
|
||||
是否有效
|
||||
"""
|
||||
return True
|
||||
215
uni-agent/adapters/lmos.py
Normal file
215
uni-agent/adapters/lmos.py
Normal file
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
LMOS (Language Model Operating System) 适配器
|
||||
Eclipse 基金会孵化的企业级多 Agent 平台
|
||||
|
||||
参考: https://eclipse.dev/lmos/
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class LMOSAdapter(ProtocolAdapter):
|
||||
"""LMOS 协议适配器"""
|
||||
|
||||
protocol_name = "lmos"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._registry_cache: Dict[str, List[dict]] = {}
|
||||
|
||||
async def _discover_via_mdns(self) -> List[dict]:
|
||||
"""通过 mDNS 发现本地 Agent(简化实现)"""
|
||||
return []
|
||||
|
||||
async def _query_registry(self, registry_url: str, capability: str = "") -> List[dict]:
|
||||
"""查询 Agent 注册中心"""
|
||||
if registry_url in self._registry_cache:
|
||||
return self._registry_cache[registry_url]
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
params = {}
|
||||
if capability:
|
||||
params["capability"] = capability
|
||||
|
||||
async with session.get(
|
||||
f"{registry_url}/agents",
|
||||
params=params,
|
||||
timeout=aiohttp.ClientTimeout(total=15)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
agents = result.get("agents", [])
|
||||
self._registry_cache[registry_url] = agents
|
||||
return agents
|
||||
return []
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
registry_url = agent_config.get("registry_url")
|
||||
|
||||
if not endpoint and not registry_url:
|
||||
raise ValueError("LMOS Agent 配置必须包含 endpoint 或 registry_url")
|
||||
|
||||
if registry_url and not endpoint:
|
||||
agent_id = agent_config.get("id")
|
||||
agents = await self._query_registry(registry_url)
|
||||
for agent in agents:
|
||||
if agent.get("id") == agent_id:
|
||||
endpoint = agent.get("endpoint")
|
||||
break
|
||||
|
||||
if not endpoint:
|
||||
raise ValueError(f"在注册中心未找到 Agent: {agent_id}")
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=None,
|
||||
metadata={
|
||||
"registry_url": registry_url,
|
||||
"group": agent_config.get("group"),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 LMOS Agent"""
|
||||
endpoint = connection.endpoint
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if method == "invoke":
|
||||
payload = {
|
||||
"capability": params.get("capability"),
|
||||
"input": params.get("input", {}),
|
||||
"context": params.get("context", {}),
|
||||
}
|
||||
elif method == "route":
|
||||
payload = {
|
||||
"query": params.get("query"),
|
||||
"context": params.get("context", {}),
|
||||
}
|
||||
elif method == "describe":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/capabilities",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
else:
|
||||
payload = {
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/invoke",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {"success": False, "error": f"HTTP {resp.status}: {error_text}"}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
all_agents = []
|
||||
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "lmos":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
all_agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@lmos",
|
||||
protocol="lmos",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "lmos":
|
||||
continue
|
||||
|
||||
registry_url = agent.get("registry_url")
|
||||
if registry_url:
|
||||
try:
|
||||
remote_agents = await self._query_registry(registry_url, capability)
|
||||
for ra in remote_agents:
|
||||
all_agents.append(AgentInfo(
|
||||
id=f"{ra['id']}@lmos",
|
||||
protocol="lmos",
|
||||
name=ra.get("name", ra["id"]),
|
||||
endpoint=ra.get("endpoint", ""),
|
||||
metadata=ra
|
||||
))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return all_agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取支持的方法"""
|
||||
result = await self.call(connection, "describe", {})
|
||||
|
||||
if result.get("success"):
|
||||
capabilities = result.get("result", {}).get("capabilities", [])
|
||||
return [
|
||||
{
|
||||
"name": cap.get("id", cap.get("name")),
|
||||
"description": cap.get("description", ""),
|
||||
"inputSchema": cap.get("inputSchema", {}),
|
||||
}
|
||||
for cap in capabilities
|
||||
]
|
||||
|
||||
return [
|
||||
{"name": "invoke", "description": "调用 Agent 能力"},
|
||||
{"name": "route", "description": "智能路由到最佳 Agent"},
|
||||
{"name": "describe", "description": "获取 Agent 能力描述"},
|
||||
]
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config or "registry_url" in agent_config
|
||||
159
uni-agent/adapters/mcp.py
Normal file
159
uni-agent/adapters/mcp.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
MCP (Model Context Protocol) 适配器
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
try:
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
HAS_MCP = True
|
||||
except ImportError:
|
||||
HAS_MCP = False
|
||||
|
||||
|
||||
class MCPAdapter(ProtocolAdapter):
|
||||
"""MCP 协议适配器"""
|
||||
|
||||
protocol_name = "mcp"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._sessions: Dict[str, Any] = {}
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
if not HAS_MCP:
|
||||
raise ImportError("请安装 mcp 库: pip install mcp")
|
||||
|
||||
command = agent_config.get("command")
|
||||
args = agent_config.get("args", [])
|
||||
env = agent_config.get("env", {})
|
||||
|
||||
if not command:
|
||||
raise ValueError("MCP Agent 配置必须包含 command")
|
||||
|
||||
full_env = os.environ.copy()
|
||||
for k, v in env.items():
|
||||
if v.startswith("${") and v.endswith("}"):
|
||||
env_var = v[2:-1]
|
||||
full_env[k] = os.environ.get(env_var, "")
|
||||
else:
|
||||
full_env[k] = v
|
||||
|
||||
server_params = StdioServerParameters(
|
||||
command=command,
|
||||
args=args,
|
||||
env=full_env
|
||||
)
|
||||
|
||||
read, write = await stdio_client(server_params).__aenter__()
|
||||
session = ClientSession(read, write)
|
||||
await session.__aenter__()
|
||||
await session.initialize()
|
||||
|
||||
agent_id = agent_config.get("id", "")
|
||||
self._sessions[agent_id] = {
|
||||
"session": session,
|
||||
"read": read,
|
||||
"write": write,
|
||||
}
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_id,
|
||||
protocol=self.protocol_name,
|
||||
endpoint=f"{command} {' '.join(args)}",
|
||||
session=session,
|
||||
metadata=agent_config
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 MCP 工具"""
|
||||
session: ClientSession = connection.session
|
||||
|
||||
result = await asyncio.wait_for(
|
||||
session.call_tool(method, params),
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if hasattr(result, "content"):
|
||||
content = result.content
|
||||
if isinstance(content, list) and len(content) > 0:
|
||||
first = content[0]
|
||||
if hasattr(first, "text"):
|
||||
return {"success": True, "result": first.text}
|
||||
return {"success": True, "result": str(first)}
|
||||
return {"success": True, "result": content}
|
||||
|
||||
return {"success": True, "result": result}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent(从本地配置)"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "mcp":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@mcp",
|
||||
protocol="mcp",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=f"{agent.get('command', '')} {' '.join(agent.get('args', []))}",
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
agent_id = connection.agent_id
|
||||
if agent_id in self._sessions:
|
||||
session_info = self._sessions.pop(agent_id)
|
||||
session = session_info.get("session")
|
||||
if session:
|
||||
await session.__aexit__(None, None, None)
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取 MCP Server 支持的工具"""
|
||||
session: ClientSession = connection.session
|
||||
|
||||
result = await session.list_tools()
|
||||
|
||||
tools = []
|
||||
if hasattr(result, "tools"):
|
||||
for tool in result.tools:
|
||||
tools.append({
|
||||
"name": tool.name,
|
||||
"description": getattr(tool, "description", ""),
|
||||
"inputSchema": getattr(tool, "inputSchema", {}),
|
||||
})
|
||||
|
||||
return tools
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "command" in agent_config
|
||||
Reference in New Issue
Block a user