Initial commit to git.yoin

This commit is contained in:
hmo
2026-02-11 22:02:47 +08:00
commit cf10ab6473
153 changed files with 14581 additions and 0 deletions

View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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