Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
74 changes: 72 additions & 2 deletions python/uagents-core/uagents_core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from typing import Any, Literal
from typing import Annotated, Any, Literal

from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict, Field, field_validator

JsonStr = str

Expand Down Expand Up @@ -106,3 +106,73 @@ def sync_resolve(self, destination: str) -> list[str]:
list[str]: The resolved endpoints.
"""
raise NotImplementedError


class AgentGeoLocationDetails(BaseModel):
model_config = ConfigDict(
extra="allow",
)

name: str | None = Field(default=None, description="the full agent location")
description: str | None = Field(
default=None, description="the description of the agent location"
)
latitude: float | None = Field(
default=None, description="the latitude of the agent location"
)
longitude: float | None = Field(
default=None, description="the longitude of the agent location"
)
street: str | None = Field(
default=None, description="the street where the agent is located"
)
city: str | None = Field(
default=None, description="the city where the agent is located"
)
state: str | None = Field(
default=None, description="the state where the agent is located"
)
postal_code: str | None = Field(
default=None, description="the postal code where the agent is located"
)
country: str | None = Field(
default=None, description="the country where the agent is located"
)
url: str | None = Field(
default=None, description="the url belonging to the agent location"
)
image_url: str | None = Field(
default=None, description="the image url belonging to the agent location"
)


class AgentGeolocation(BaseModel):
model_config = ConfigDict(strict=True, allow_inf_nan=False, extra="forbid")
latitude: Annotated[float, Field(ge=-90, le=90)]
longitude: Annotated[float, Field(ge=-180, le=180)]
radius: Annotated[float, Field(ge=0)] = 0

@field_validator("latitude", "longitude")
@classmethod
def serialize_precision(cls, val: float) -> float:
"""
Round the latitude and longitude to 6 decimal places.
Equivalent to 0.11m precision.
"""
return round(val, 6)


class AgentMetadata(BaseModel):
"""
Model used to validate metadata for an agent.

Framework specific fields will be added here to ensure valid serialization.
Additional fields will simply be passed through.
"""

model_config = ConfigDict(
extra="allow",
arbitrary_types_allowed=True,
)

geolocation: AgentGeolocation | AgentGeoLocationDetails | None = None
33 changes: 27 additions & 6 deletions python/uagents-core/uagents_core/utils/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
ChallengeResponse,
RegistrationRequest,
)
from uagents_core.types import AddressPrefix, AgentEndpoint, AgentType
from uagents_core.types import AddressPrefix, AgentEndpoint, AgentMetadata, AgentType

logger = get_logger("uagents_core.utils.registration")

Expand Down Expand Up @@ -70,6 +70,10 @@ class AgentverseRegistrationRequest(BaseModel):
protocols: list[str] = Field(
description="List of protocols supported by the agent."
)
metadata: dict[str, str | list[str] | dict[str, str]] | None = Field(
default=None,
description="Additional metadata about the agent (e.g. geolocation).",
)
type: AgentType = Field(
default="custom", description="Agentverse registration type"
)
Expand Down Expand Up @@ -204,7 +208,7 @@ def _register_in_almanac(
identity: Identity,
endpoints: list[str],
protocol_digests: list[str],
metadata: dict[str, str | list[str] | dict[str, str]] | None = None,
metadata: AgentMetadata | dict[str, str | list[str] | dict[str, str]] | None = None,
prefix: AddressPrefix | None = None,
*,
agentverse_config: AgentverseConfig | None = None,
Expand All @@ -225,13 +229,19 @@ def _register_in_almanac(
agentverse_config = agentverse_config or AgentverseConfig()
almanac_api = urllib.parse.urljoin(agentverse_config.url, DEFAULT_ALMANAC_API_PATH)

raw_metadata = (
metadata.model_dump(exclude_unset=True)
if isinstance(metadata, AgentMetadata)
else metadata
)

# create the attestation
item = AgentRegistrationInput(
identity=identity,
prefix=prefix,
endpoints=endpoints,
protocol_digests=protocol_digests,
metadata=metadata,
metadata=raw_metadata,
)
attestation = _build_signed_attestation(item)

Expand All @@ -247,7 +257,7 @@ def register_in_almanac(
identity: Identity,
endpoints: list[str],
protocol_digests: list[str],
metadata: dict[str, str | list[str] | dict[str, str]] | None = None,
metadata: AgentMetadata | dict[str, str | list[str] | dict[str, str]] | None = None,
prefix: AddressPrefix | None = None,
*,
agentverse_config: AgentverseConfig | None = None,
Expand All @@ -261,6 +271,8 @@ def register_in_almanac(
prefix (AddressPrefix | None): The prefix for the agent identifier.
endpoints (list[str]): The endpoints that the agent can be reached at.
protocol_digests (list[str]): The digests of the protocol that the agent supports
metadata (AgentMetadata | dict[str, str | list[str] | dict[str, str]] | None):
Additional metadata about the agent (e.g. geolocation).
agentverse_config (AgentverseConfig): The configuration for the agentverse API
timeout (int): The timeout for the request
"""
Expand Down Expand Up @@ -593,6 +605,7 @@ def register_agent(
identity = Identity.from_seed(credentials.agent_seed_phrase, 0)
endpoints = [agent_registration.endpoint]
protos = agent_registration.protocols
metadata = agent_registration.metadata

connect_request = AgentverseConnectRequest(
user_token=credentials.agentverse_api_key,
Expand All @@ -612,7 +625,7 @@ def register_agent(
try:
logger.info("registering to Almanac...")
_register_in_almanac(
identity, endpoints, protos, agentverse_config=agentverse_config
identity, endpoints, protos, metadata, agentverse_config=agentverse_config
)
logger.info("successfully registered to Almanac.")
except AgentverseRequestError as e:
Expand Down Expand Up @@ -651,10 +664,17 @@ def register_chat_agent(
description: str | None = None,
readme: str | None = None,
avatar_url: str | None = None,
metadata: AgentMetadata | dict[str, str | list[str] | dict[str, str]] | None = None,
agentverse_config: AgentverseConfig | None = None,
):
chat_protocol = [
ProtocolSpecification.compute_digest(chat_protocol_spec.manifest())
]
raw_metadata = (
metadata.model_dump(exclude_unset=True)
if isinstance(metadata, AgentMetadata)
else metadata
)
request = AgentverseRegistrationRequest(
name=name,
endpoint=endpoint,
Expand All @@ -663,7 +683,8 @@ def register_chat_agent(
description=description,
readme=readme,
avatar_url=avatar_url,
metadata=raw_metadata,
)
config = AgentverseConfig()
config = agentverse_config or AgentverseConfig()

register_agent(request, config, credentials)