Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): updates to mailbox and agentverse registration process #588

Merged
merged 18 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 5 additions & 36 deletions python/docs/api/uagents/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ An agent that interacts within a communication environment.
- `identifier` _str_ - The Agent Identifier, including network prefix and address.
- `wallet` _LocalWallet_ - The agent's wallet for transacting on the ledger.
- `storage` _KeyValueStore_ - The key-value store for storage operations.
- `mailbox` _Dict[str, str]_ - The mailbox configuration for the agent.
- `agentverse` _Dict[str, str]_ - The agentverse configuration for the agent.
- `agentverse` _AgentverseConfig_ - The agentverse configuration for the agent.
- `mailbox_client` _MailboxClient_ - The client for interacting with the agentverse mailbox.
- `protocols` _Dict[str, Protocol]_ - Dictionary mapping all supported protocol digests to their
corresponding protocols.
Expand All @@ -185,7 +184,8 @@ def __init__(name: Optional[str] = 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,
mailbox: bool = False,
proxy: bool = False,
resolve: Optional[Resolver] = None,
registration_policy: Optional[AgentRegistrationPolicy] = None,
enable_wallet_messaging: Union[bool, Dict[str, str]] = False,
Expand All @@ -208,7 +208,8 @@ Initialize an Agent instance.
- `seed` _Optional[str]_ - The seed for generating keys.
- `endpoint` _Optional[Union[str, List[str], Dict[str, dict]]]_ - The endpoint configuration.
- `agentverse` _Optional[Union[str, Dict[str, str]]]_ - The agentverse configuration.
- `mailbox` _Optional[Union[str, Dict[str, str]]]_ - The mailbox configuration.
- `mailbox` _bool_ - True if the agent will receive messages via an Agentverse mailbox.
- `proxy` _bool_ - True if the agent will receive messages via an Agentverse proxy endpoint.
- `resolve` _Optional[Resolver]_ - The resolver to use for agent communication.
- `enable_wallet_messaging` _Optional[Union[bool, Dict[str, str]]]_ - Whether to enable
wallet messaging. If '{"chain_id": CHAIN_ID}' is provided, this sets the chain ID for
Expand Down Expand Up @@ -328,22 +329,6 @@ Get the key-value store used by the agent for data storage.

- `KeyValueStore` - The key-value store instance.

<a id="src.uagents.agent.Agent.mailbox"></a>

#### mailbox

```python
@property
def mailbox() -> AgentverseConfig
```

Get the mailbox configuration of the agent.
Agentverse overrides it but mailbox is kept for backwards compatibility.

**Returns**:

Dict[str, str]: The mailbox configuration.

<a id="src.uagents.agent.Agent.agentverse"></a>

#### agentverse
Expand Down Expand Up @@ -419,22 +404,6 @@ Get the metadata associated with the agent.

Dict[str, Any]: The metadata associated with the agent.

<a id="src.uagents.agent.Agent.mailbox"></a>

#### mailbox

```python
@mailbox.setter
def mailbox(config: Union[str, Dict[str, str]])
```

Set the mailbox configuration for the agent.
Agentverse overrides it but mailbox is kept for backwards compatibility.

**Arguments**:

- `config` _Union[str, Dict[str, str]]_ - The new mailbox configuration.

<a id="src.uagents.agent.Agent.agentverse"></a>

#### agentverse
Expand Down
18 changes: 15 additions & 3 deletions python/docs/api/uagents/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,27 @@

```python
def parse_endpoint_config(
endpoint: Optional[Union[str, List[str], Dict[str, dict]]]
) -> List[AgentEndpoint]
endpoint: Optional[Union[str, List[str], Dict[str, dict]]],
agentverse: AgentverseConfig,
mailbox: bool = False,
proxy: bool = False,
logger: Optional[logging.Logger] = None) -> List[AgentEndpoint]
```

Parse the user-provided endpoint configuration.

**Arguments**:

- `endpoint` _Optional[Union[str, List[str], Dict[str, dict]]]_ - The endpoint configuration.
- `agentverse` _AgentverseConfig_ - The agentverse configuration.
- `mailbox` _bool_ - Whether to use the mailbox endpoint.
- `proxy` _bool_ - Whether to use the proxy endpoint.
- `logger` _Optional[logging.Logger]_ - The logger to use.


**Returns**:

Optional[List[Dict[str, Any]]]: The parsed endpoint configuration.
- `Optional[List[AgentEndpoint]` - The parsed endpoint configuration.

<a id="src.uagents.config.parse_agentverse_config"></a>

Expand Down
12 changes: 2 additions & 10 deletions python/examples/11-mailbox-agents/alice.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@ class Message(Model):
message: str


# First generate a secure seed phrase (e.g. https://pypi.org/project/mnemonic/)
SEED_PHRASE = "put_your_seed_phrase_here"

# Now your agent is ready to join the agentverse!
agent = Agent(
name="alice",
seed=SEED_PHRASE,
port=8000,
endpoint="http://agentverse.ai/v1/submit",
)
agent = Agent(name="alice", port=8008, mailbox=True)


@agent.on_message(model=Message, replies={Message})
@agent.on_message(model=Message, replies=Message)
async def handle_message(ctx: Context, sender: str, msg: Message):
ctx.logger.info(f"Received message from {sender}: {msg.message}")

Expand Down
9 changes: 1 addition & 8 deletions python/examples/11-mailbox-agents/bob.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,9 @@ class Message(Model):
# Copy ALICE_ADDRESS generated in alice.py
ALICE_ADDRESS = "paste_alice_address_here"

# Generate a second seed phrase (e.g. https://pypi.org/project/mnemonic/)
SEED_PHRASE = "put_your_seed_phrase_here"

# Now your agent is ready to join the agentverse!
agent = Agent(
name="bob",
seed=SEED_PHRASE,
port=8001,
endpoint="http://agentverse.ai/v1/submit",
)
agent = Agent(name="bob", port=8009, mailbox=True)


@agent.on_interval(period=2.0)
Expand Down
62 changes: 13 additions & 49 deletions python/src/uagents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,7 @@ class Agent(Sink):
identifier (str): The Agent Identifier, including network prefix and address.
wallet (LocalWallet): The agent's wallet for transacting on the ledger.
storage (KeyValueStore): The key-value store for storage operations.
mailbox (Dict[str, str]): The mailbox configuration for the agent.
agentverse (Dict[str, str]): The agentverse configuration for the agent.
agentverse (AgentverseConfig): The agentverse configuration for the agent.
mailbox_client (MailboxClient): The client for interacting with the agentverse mailbox.
protocols (Dict[str, Protocol]): Dictionary mapping all supported protocol digests to their
corresponding protocols.
Expand All @@ -273,7 +272,8 @@ def __init__(
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,
mailbox: bool = False,
proxy: bool = False,
resolve: Optional[Resolver] = None,
registration_policy: Optional[AgentRegistrationPolicy] = None,
enable_wallet_messaging: Union[bool, Dict[str, str]] = False,
Expand All @@ -295,7 +295,8 @@ def __init__(
seed (Optional[str]): The seed for generating keys.
endpoint (Optional[Union[str, List[str], Dict[str, dict]]]): The endpoint configuration.
agentverse (Optional[Union[str, Dict[str, str]]]): The agentverse configuration.
mailbox (Optional[Union[str, Dict[str, str]]]): The mailbox configuration.
mailbox (bool): True if the agent will receive messages via an Agentverse mailbox.
proxy (bool): True if the agent will receive messages via an Agentverse proxy endpoint.
resolve (Optional[Resolver]): The resolver to use for agent communication.
enable_wallet_messaging (Optional[Union[bool, Dict[str, str]]]): Whether to enable
wallet messaging. If '{"chain_id": CHAIN_ID}' is provided, this sets the chain ID for
Expand All @@ -319,29 +320,12 @@ def __init__(
self._initialize_wallet_and_identity(seed, name, wallet_key_derivation_index)
self._logger = get_logger(self.name, level=log_level)

# configure endpoints and mailbox
self._endpoints = parse_endpoint_config(endpoint)
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._agentverse = parse_agentverse_config(agentverse)

# support legacy method of setting mailbox configuration if no endpoint is provided
if endpoint is None and mailbox:
self._logger.info(
f"Setting endpoint to mailbox: {self._agentverse.url}/v1/submit"
)
self._endpoints = [
AgentEndpoint(url=f"{self._agentverse.url}/v1/submit", weight=1)
]
# configure endpoints and mailbox
self._endpoints = parse_endpoint_config(
endpoint, self._agentverse, mailbox, proxy, self._logger
)

self._use_mailbox = is_mailbox_agent(self._endpoints, self._agentverse)
if self._use_mailbox:
Expand Down Expand Up @@ -613,17 +597,6 @@ def storage(self) -> KeyValueStore:
"""
return self._storage

@property
def mailbox(self) -> AgentverseConfig:
"""
Get the mailbox configuration of the agent.
Agentverse overrides it but mailbox is kept for backwards compatibility.

Returns:
Dict[str, str]: The mailbox configuration.
"""
return self._agentverse

@property
def agentverse(self) -> AgentverseConfig:
"""
Expand Down Expand Up @@ -680,17 +653,6 @@ def metadata(self) -> Dict[str, Any]:
"""
return self._metadata

@mailbox.setter
def mailbox(self, config: Union[str, Dict[str, str]]):
"""
Set the mailbox configuration for the agent.
Agentverse overrides it but mailbox is kept for backwards compatibility.

Args:
config (Union[str, Dict[str, str]]): The new mailbox configuration.
"""
self._agentverse = parse_agentverse_config(config)

@agentverse.setter
def agentverse(self, config: Union[str, Dict[str, str]]):
"""
Expand Down Expand Up @@ -937,7 +899,7 @@ def _on_rest(
return lambda func: func

def decorator_on_rest(func: RestHandler):
@functools.wraps(RestGetHandler if method == "GET" else RestPostHandler)
@functools.wraps(RestGetHandler if method == "GET" else RestPostHandler) # type: ignore
def handler(*args, **kwargs):
return func(*args, **kwargs)

Expand Down Expand Up @@ -1412,7 +1374,6 @@ def __init__(
"""
self._loop = loop or asyncio.get_event_loop_policy().get_event_loop()
self._agents: List[Agent] = []
self._endpoints = parse_endpoint_config(endpoint)
self._port = port or 8000
self._queries: Dict[str, asyncio.Future] = {}
self._logger = get_logger("bureau", log_level)
Expand All @@ -1423,6 +1384,9 @@ def __init__(
logger=self._logger,
)
self._agentverse = parse_agentverse_config(agentverse)
self._endpoints = parse_endpoint_config(
endpoint, self._agentverse, False, False, self._logger
)
self._use_mailbox = any(
[
is_mailbox_agent(agent._endpoints, self._agentverse)
Expand Down
28 changes: 18 additions & 10 deletions python/src/uagents/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,27 @@ async def _asgi_send(
body: Optional[Union[Dict[str, Any], ErrorWrapper]] = None,
):
header = (
[[k.encode(), v.encode()] for k, v in headers.items()] if headers else None
[[k.encode(), v.encode()] for k, v in headers.items()]
if headers is not None
else [[b"content-type", b"application/json"]]
)
if body is None:
body = {}

await send(
{
"type": "http.response.start",
"status": status_code,
"headers": header or [[b"content-type", b"application/json"]],
"headers": header,
}
)
await send({"type": "http.response.body", "body": json.dumps(body).encode()})

if body is None:
encoded_body = (
b"{}" if [[b"content-type", b"application/json"]] in header else b""
)
else:
encoded_body = json.dumps(body).encode()

await send({"type": "http.response.body", "body": encoded_body})

async def handle_readiness_probe(self, headers: CaseInsensitiveDict, send):
"""
Expand All @@ -154,7 +162,7 @@ async def handle_readiness_probe(self, headers: CaseInsensitiveDict, send):
if b"x-uagents-address" not in headers:
await self._asgi_send(send, headers={"x-uagents-status": "indeterminate"})
else:
address = headers[b"x-uagents-address"].decode()
address = headers[b"x-uagents-address"].decode() # type: ignore
if not dispatcher.contains(address):
await self._asgi_send(send, headers={"x-uagents-status": "not-ready"})
else:
Expand Down Expand Up @@ -224,7 +232,7 @@ async def _handle_rest(
},
)
return
destination = headers[b"x-uagents-address"].decode()
destination = headers[b"x-uagents-address"].decode() # type: ignore
rest_handler = handlers.get(destination)
else:
destination, rest_handler = handlers.popitem()
Expand Down Expand Up @@ -296,7 +304,7 @@ async def __call__(self, scope, receive, send): # pylint: disable=too-many-bra

# Handle OPTIONS preflight request for CORS
if request_method == "OPTIONS":
await self._asgi_send(send, 204)
await self._asgi_send(send, 204, headers={})
return

# check if the request is for a REST endpoint
Expand All @@ -321,7 +329,7 @@ async def __call__(self, scope, receive, send): # pylint: disable=too-many-bra
await self.handle_missing_content_type(headers, send)
return

if b"application/json" not in headers[b"content-type"]:
if b"application/json" not in headers[b"content-type"]: # type: ignore
await self._asgi_send(send, 400, body={"error": "invalid content-type"})
return

Expand All @@ -342,7 +350,7 @@ async def __call__(self, scope, receive, send): # pylint: disable=too-many-bra
)
return

expects_response = headers.get(b"x-uagents-connection") == b"sync"
expects_response = headers.get(b"x-uagents-connection") == b"sync" # type: ignore

if expects_response:
# Add a future that will be resolved once the query is answered
Expand Down
Loading
Loading