From 01ae9d1f90db208837cbd380ac130a2e9f14ad08 Mon Sep 17 00:00:00 2001 From: James Riehl <33920192+jrriehl@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:29:28 +0100 Subject: [PATCH] feat: publish protocol manifest to agentverse (#109) --- .../09-booking-protocol-demo/restaurant.py | 8 +- src/uagents/agent.py | 59 +++++++-- src/uagents/config.py | 32 +++-- tests/test_config.py | 112 ++++++++++++++++++ 4 files changed, 186 insertions(+), 25 deletions(-) create mode 100644 tests/test_config.py diff --git a/examples/09-booking-protocol-demo/restaurant.py b/examples/09-booking-protocol-demo/restaurant.py index 0551d63e..b74ca64a 100644 --- a/examples/09-booking-protocol-demo/restaurant.py +++ b/examples/09-booking-protocol-demo/restaurant.py @@ -17,10 +17,10 @@ fund_agent_if_low(restaurant.wallet.address()) -# build the restaurant agent from stock protocols -restaurant.include(query_proto) -restaurant.include(book_proto) -restaurant.include(proto_query) +# build the restaurant agent from stock protocols and publish their details +restaurant.include(query_proto, publish_manifest=True) +restaurant.include(book_proto, publish_manifest=True) +restaurant.include(proto_query, publish_manifest=True) TABLES = { 1: TableStatus(seats=2, time_start=16, time_end=22), diff --git a/src/uagents/agent.py b/src/uagents/agent.py index c558d1ae..d1e67381 100644 --- a/src/uagents/agent.py +++ b/src/uagents/agent.py @@ -2,6 +2,7 @@ import functools from typing import Dict, List, Optional, Set, Union, Type, Tuple, Any import uuid +import requests from cosmpy.aerial.wallet import LocalWallet, PrivateKey from cosmpy.crypto.address import Address @@ -38,7 +39,7 @@ MIN_REGISTRATION_TIME, LEDGER_PREFIX, parse_endpoint_config, - parse_mailbox_config, + parse_agentverse_config, get_logger, ) @@ -66,6 +67,7 @@ def __init__( port: Optional[int] = None, seed: Optional[str] = None, endpoint: Optional[Union[str, List[str], Dict[str, dict]]] = None, + agentverse: Optional[Union[str, Dict[str, str]]] = None, mailbox: Optional[Union[str, Dict[str, str]]] = None, resolve: Optional[Resolver] = None, version: Optional[str] = None, @@ -76,15 +78,29 @@ def __init__( self._resolver = resolve if resolve is not None else GlobalResolver() self._loop = asyncio.get_event_loop_policy().get_event_loop() - # initialize wallet and identity self._initialize_wallet_and_identity(seed, name) + self._logger = get_logger(self.name) # configure endpoints and mailbox self._endpoints = parse_endpoint_config(endpoint) - self._use_mailbox = mailbox is not None + self._use_mailbox = False + + if mailbox: + # agentverse config overrides mailbox config + # but mailbox is kept for backwards compatibility + if agentverse: + self._logger.warning( + "Ignoring the provided 'mailbox' configuration since 'agentverse' overrides it" + ) + else: + agentverse = mailbox + self._logger.warning( + "The 'mailbox' configuration is deprecated in favor of 'agentverse'" + ) + self._agentverse = parse_agentverse_config(agentverse) + self._use_mailbox = self._agentverse["use_mailbox"] if self._use_mailbox: - self._mailbox = parse_mailbox_config(mailbox) self._mailbox_client = MailboxClient(self, self._logger) # if mailbox is provided, override endpoints with mailbox endpoint self._endpoints = [ @@ -94,7 +110,6 @@ def __init__( } ] else: - self._mailbox = None self._mailbox_client = None self._ledger = get_ledger() @@ -179,7 +194,11 @@ def storage(self) -> KeyValueStore: @property def mailbox(self) -> Dict[str, str]: - return self._mailbox + return self._agentverse + + @property + def agentverse(self) -> Dict[str, str]: + return self._agentverse @property def mailbox_client(self) -> MailboxClient: @@ -187,7 +206,11 @@ def mailbox_client(self) -> MailboxClient: @mailbox.setter def mailbox(self, config: Union[str, Dict[str, str]]): - self._mailbox = parse_mailbox_config(config) + self._agentverse = parse_agentverse_config(config) + + @agentverse.setter + def agentverse(self, config: Union[str, Dict[str, str]]): + self._agentverse = parse_agentverse_config(config) def sign(self, data: bytes) -> str: return self._identity.sign(data) @@ -318,7 +341,7 @@ def _add_event_handler( elif event_type == "shutdown": self._on_shutdown.append(func) - def include(self, protocol: Protocol): + def include(self, protocol: Protocol, publish_manifest: Optional[bool] = False): for func, period in protocol.intervals: self._interval_handlers.append((func, period)) @@ -348,6 +371,26 @@ def include(self, protocol: Protocol): if protocol.digest is not None: self.protocols[protocol.digest] = protocol + if publish_manifest: + self.publish_manifest(protocol.manifest()) + + def publish_manifest(self, manifest: Dict[str, Any]): + try: + resp = requests.post( + f"{self._agentverse['http_prefix']}://{self._agentverse['base_url']}" + + "/v1/almanac/manifests", + json=manifest, + timeout=5, + ) + if resp.status_code == 200: + self._logger.info( + f"Manifest published successfully: {manifest['metadata']['name']}" + ) + else: + self._logger.warning(f"Unable to publish manifest: {resp.text}") + except requests.exceptions.RequestException as ex: + self._logger.warning(f"Unable to publish manifest: {ex}") + async def handle_message( self, sender, schema_digest: str, message: JsonStr, session: uuid.UUID ): diff --git a/src/uagents/config.py b/src/uagents/config.py index 0190edb3..b56e3414 100644 --- a/src/uagents/config.py +++ b/src/uagents/config.py @@ -26,7 +26,7 @@ class AgentNetwork(Enum): BLOCK_INTERVAL = 5 AGENT_NETWORK = AgentNetwork.FETCHAI_TESTNET -MAILBOX_SERVER_URL = "wss://agentverse.ai" +AGENTVERSE_URL = "https://agentverse.ai" MAILBOX_POLL_INTERVAL_SECONDS = 1.0 DEFAULT_ENVELOPE_TIMEOUT_SECONDS = 30 @@ -49,28 +49,34 @@ def parse_endpoint_config( return endpoints -def parse_mailbox_config(mailbox: Union[str, Dict[str, str]]) -> Dict[str, str]: +def parse_agentverse_config( + config: Optional[Union[str, Dict[str, str]]] = None, +) -> Dict[str, str]: api_key = None - base_url = MAILBOX_SERVER_URL - if isinstance(mailbox, str): - if mailbox.count("@") == 1: - api_key, base_url = mailbox.split("@") + base_url = AGENTVERSE_URL + protocol = None + protocol_override = None + if isinstance(config, str): + if config.count("@") == 1: + api_key, base_url = config.split("@") + elif "://" in config: + base_url = config else: - api_key = mailbox - elif isinstance(mailbox, dict): - api_key = mailbox.get("api_key") - base_url = mailbox.get("base_url") - protocol = mailbox.get("protocol") or "http" + api_key = config + elif isinstance(config, dict): + api_key = config.get("api_key") + base_url = config.get("base_url") or base_url + protocol_override = config.get("protocol") if "://" in base_url: protocol, base_url = base_url.split("://") - else: - protocol = "wss" + protocol = protocol_override or protocol or "https" http_prefix = "https" if protocol in {"wss", "https"} else "http" return { "api_key": api_key, "base_url": base_url, "protocol": protocol, "http_prefix": http_prefix, + "use_mailbox": api_key is not None, } diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 00000000..481e4396 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,112 @@ +import unittest + +from uagents import Agent + + +agents = [ + Agent(), + Agent(mailbox="api_key@some_url"), + Agent(mailbox={"api_key": "api_key", "base_url": "some_url"}), + Agent(mailbox="api_key"), + Agent(agentverse="api_key@some_url"), + Agent(agentverse="api_key"), + Agent(agentverse="http://some_url"), + Agent(agentverse="wss://some_url"), + Agent(agentverse="ws://some_url"), + Agent(agentverse={"api_key": "api_key", "protocol": "wss"}), + Agent(agentverse="https://staging.agentverse.ai"), + Agent(agentverse={"base_url": "staging.agentverse.ai"}), +] + +expected_configs = [ + { + "api_key": None, + "base_url": "agentverse.ai", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": False, + }, + { + "api_key": "api_key", + "base_url": "some_url", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": True, + }, + { + "api_key": "api_key", + "base_url": "some_url", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": True, + }, + { + "api_key": "api_key", + "base_url": "agentverse.ai", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": True, + }, + { + "api_key": "api_key", + "base_url": "some_url", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": True, + }, + { + "api_key": "api_key", + "base_url": "agentverse.ai", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": True, + }, + { + "api_key": None, + "base_url": "some_url", + "protocol": "http", + "http_prefix": "http", + "use_mailbox": False, + }, + { + "api_key": None, + "base_url": "some_url", + "protocol": "wss", + "http_prefix": "https", + "use_mailbox": False, + }, + { + "api_key": None, + "base_url": "some_url", + "protocol": "ws", + "http_prefix": "http", + "use_mailbox": False, + }, + { + "api_key": "api_key", + "base_url": "agentverse.ai", + "protocol": "wss", + "http_prefix": "https", + "use_mailbox": True, + }, + { + "api_key": None, + "base_url": "staging.agentverse.ai", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": False, + }, + { + "api_key": None, + "base_url": "staging.agentverse.ai", + "protocol": "https", + "http_prefix": "https", + "use_mailbox": False, + }, +] + + +class TestConfig(unittest.TestCase): + def test_parse_agentverse_config(self): + for agent, expected_config in zip(agents, expected_configs): + self.assertEqual(agent.agentverse, expected_config)