uagents-core 0.3.5__tar.gz → 0.3.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {uagents_core-0.3.5 → uagents_core-0.3.7}/PKG-INFO +1 -1
  2. {uagents_core-0.3.5 → uagents_core-0.3.7}/pyproject.toml +1 -1
  3. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/contrib/protocols/chat/__init__.py +54 -1
  4. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/types.py +2 -0
  5. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/utils/messages.py +83 -21
  6. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/utils/resolver.py +10 -3
  7. {uagents_core-0.3.5 → uagents_core-0.3.7}/README.md +0 -0
  8. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/__init__.py +0 -0
  9. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/config.py +0 -0
  10. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/contrib/__init__.py +0 -0
  11. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/contrib/protocols/__init__.py +0 -0
  12. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/contrib/protocols/subscriptions/__init__.py +0 -0
  13. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/envelope.py +0 -0
  14. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/helpers.py +0 -0
  15. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/identity.py +0 -0
  16. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/logger.py +0 -0
  17. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/models.py +0 -0
  18. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/protocol.py +0 -0
  19. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/registration.py +0 -0
  20. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/storage.py +0 -0
  21. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/utils/__init__.py +0 -0
  22. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/utils/registration.py +0 -0
  23. {uagents_core-0.3.5 → uagents_core-0.3.7}/uagents_core/utils/subscriptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: uagents-core
3
- Version: 0.3.5
3
+ Version: 0.3.7
4
4
  Summary: Core components for agent based systems
5
5
  License: Apache 2.0
6
6
  Author: Ed FitzGerald
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uagents-core"
3
- version = "0.3.5"
3
+ version = "0.3.7"
4
4
  description = "Core components for agent based systems"
5
5
  authors = [
6
6
  { name = "Ed FitzGerald", email = "edward.fitzgerald@fetch.ai" },
@@ -2,8 +2,9 @@
2
2
  This module contains the protocol specification for the agent chat protocol.
3
3
  """
4
4
 
5
- from datetime import datetime
5
+ from datetime import datetime, timezone
6
6
  from typing import Literal, TypedDict
7
+ from uuid import uuid4
7
8
 
8
9
  from pydantic.v1 import UUID4
9
10
 
@@ -27,6 +28,9 @@ class TextContent(Model):
27
28
  # markdown based formatting can be used and will be supported by most clients
28
29
  text: str
29
30
 
31
+ def __init__(self, text: str, type: str = "text"):
32
+ super().__init__(type=type, text=text)
33
+
30
34
 
31
35
  class Resource(Model):
32
36
  # the uri of the resource
@@ -36,6 +40,9 @@ class Resource(Model):
36
40
  # fields see `docs/metadata.md`
37
41
  metadata: dict[str, str]
38
42
 
43
+ def __init__(self, uri: str, metadata: dict[str, str] | None = None):
44
+ super().__init__(uri=uri, metadata=metadata or {})
45
+
39
46
 
40
47
  class ResourceContent(Model):
41
48
  type: Literal["resource"]
@@ -51,6 +58,14 @@ class ResourceContent(Model):
51
58
  # considered the primary resource
52
59
  resource: Resource | list[Resource]
53
60
 
61
+ def __init__(
62
+ self,
63
+ resource_id: UUID4,
64
+ resource: Resource | list[Resource],
65
+ type: str = "resource",
66
+ ):
67
+ super().__init__(type=type, resource_id=resource_id, resource=resource)
68
+
54
69
 
55
70
  class MetadataContent(Model):
56
71
  type: Literal["metadata"]
@@ -59,26 +74,41 @@ class MetadataContent(Model):
59
74
  # fields see `docs/metadata.md`
60
75
  metadata: dict[str, str]
61
76
 
77
+ def __init__(self, metadata: dict[str, str], type: str = "metadata"):
78
+ super().__init__(type=type, metadata=metadata)
79
+
62
80
 
63
81
  class StartSessionContent(Model):
64
82
  type: Literal["start-session"]
65
83
 
84
+ def __init__(self, type: str = "start-session"):
85
+ super().__init__(type=type)
86
+
66
87
 
67
88
  class EndSessionContent(Model):
68
89
  type: Literal["end-session"]
69
90
 
91
+ def __init__(self, type: str = "end-session"):
92
+ super().__init__(type=type)
93
+
70
94
 
71
95
  class StartStreamContent(Model):
72
96
  type: Literal["start-stream"]
73
97
 
74
98
  stream_id: UUID4
75
99
 
100
+ def __init__(self, stream_id: UUID4, type: str = "start-stream"):
101
+ super().__init__(type=type, stream_id=stream_id)
102
+
76
103
 
77
104
  class EndStreamContent(Model):
78
105
  type: Literal["end-stream"]
79
106
 
80
107
  stream_id: UUID4
81
108
 
109
+ def __init__(self, stream_id: UUID4, type: str = "end-stream"):
110
+ super().__init__(type=type, stream_id=stream_id)
111
+
82
112
 
83
113
  # The combined agent content types
84
114
  AgentContent = (
@@ -102,6 +132,16 @@ class ChatMessage(Model):
102
132
  # the list of content elements in the chat
103
133
  content: list[AgentContent]
104
134
 
135
+ def __init__(
136
+ self,
137
+ content: list[AgentContent],
138
+ msg_id: UUID4 | None = None,
139
+ timestamp: datetime | None = None,
140
+ ):
141
+ msg_id = msg_id or uuid4()
142
+ timestamp = timestamp or datetime.now(timezone.utc)
143
+ super().__init__(timestamp=timestamp, msg_id=msg_id, content=content)
144
+
105
145
 
106
146
  class ChatAcknowledgement(Model):
107
147
  # the timestamp for the message, should be in UTC
@@ -113,6 +153,19 @@ class ChatAcknowledgement(Model):
113
153
  # optional acknowledgement metadata
114
154
  metadata: dict[str, str] | None = None
115
155
 
156
+ def __init__(
157
+ self,
158
+ acknowledged_msg_id: UUID4,
159
+ metadata: dict[str, str] | None = None,
160
+ timestamp: datetime | None = None,
161
+ ):
162
+ timestamp = timestamp or datetime.now(timezone.utc)
163
+ super().__init__(
164
+ timestamp=timestamp,
165
+ acknowledged_msg_id=acknowledged_msg_id,
166
+ metadata=metadata,
167
+ )
168
+
116
169
 
117
170
  chat_protocol_spec = ProtocolSpecification(
118
171
  name="AgentChatProtocol",
@@ -22,6 +22,8 @@ class AgentInfo(BaseModel):
22
22
  endpoints: list[AgentEndpoint]
23
23
  protocols: list[str]
24
24
  metadata: dict[str, Any] | None = None
25
+ agent_type: AgentType
26
+ port: int | None = None
25
27
 
26
28
 
27
29
  class DeliveryStatus(str, Enum):
@@ -2,19 +2,24 @@
2
2
  This module provides methods to enable an identity to interact with other agents.
3
3
  """
4
4
 
5
+ import contextlib
5
6
  import json
6
7
  from typing import Any, Literal
7
8
  from uuid import UUID, uuid4
8
9
 
9
10
  import requests
11
+ from pydantic import ValidationError
10
12
 
11
- from uagents_core.config import DEFAULT_REQUEST_TIMEOUT, AgentverseConfig
13
+ from uagents_core.config import (
14
+ DEFAULT_MAX_ENDPOINTS,
15
+ DEFAULT_REQUEST_TIMEOUT,
16
+ AgentverseConfig,
17
+ )
12
18
  from uagents_core.envelope import Envelope
13
- from uagents_core.helpers import weighted_random_sample
14
19
  from uagents_core.identity import Identity
15
20
  from uagents_core.logger import get_logger
16
21
  from uagents_core.models import Model
17
- from uagents_core.types import DeliveryStatus, MsgStatus, Resolver
22
+ from uagents_core.types import DeliveryStatus, JsonStr, MsgStatus, Resolver
18
23
  from uagents_core.utils.resolver import AlmanacResolver
19
24
 
20
25
  logger = get_logger("uagents_core.utils.messages")
@@ -58,7 +63,10 @@ def generate_message_envelope(
58
63
 
59
64
 
60
65
  def send_message(
61
- endpoint: str, envelope: Envelope, timeout: int = DEFAULT_REQUEST_TIMEOUT
66
+ endpoint: str,
67
+ envelope: Envelope,
68
+ timeout: int = DEFAULT_REQUEST_TIMEOUT,
69
+ sync: bool = False,
62
70
  ) -> requests.Response:
63
71
  """
64
72
  A helper function to send a message to an agent.
@@ -67,13 +75,17 @@ def send_message(
67
75
  endpoint (str): The endpoint to send the message to.
68
76
  envelope (Envelope): The envelope containing the message.
69
77
  timeout (int, optional): Requests timeout. Defaults to DEFAULT_REQUEST_TIMEOUT.
78
+ sync (bool, optional): Whether to send the message synchronously. Defaults to False.
70
79
 
71
80
  Returns:
72
81
  requests.Response: Response object from the request.
73
82
  """
83
+ headers = {"content-type": "application/json"}
84
+ if sync:
85
+ headers["x-uagents-connection"] = "sync"
74
86
  response = requests.post(
75
87
  url=endpoint,
76
- headers={"content-type": "application/json"},
88
+ headers=headers,
77
89
  data=envelope.model_dump_json(),
78
90
  timeout=timeout,
79
91
  )
@@ -81,6 +93,39 @@ def send_message(
81
93
  return response
82
94
 
83
95
 
96
+ def parse_sync_response(
97
+ env_json: JsonStr,
98
+ response_type: type[Model] | set[type[Model]] | None = None,
99
+ ) -> Model | JsonStr:
100
+ """
101
+ Parse the response from a synchronous message.
102
+
103
+ Args:
104
+ env_json (JsonStr): The JSON string of the response envelope.
105
+ response_type (type[Model] | set[type[Model]] | None, optional):
106
+ The expected response type(s) for a sync message.
107
+
108
+ Returns:
109
+ Model | JsonStr: The parsed response model or JSON string.
110
+ """
111
+
112
+ env = Envelope.model_validate_json(env_json)
113
+
114
+ response_json = env.decode_payload()
115
+
116
+ response_msg: Model | None = None
117
+ if response_type:
118
+ response_types = (
119
+ {response_type} if isinstance(response_type, type) else response_type
120
+ )
121
+
122
+ for r_type in response_types:
123
+ with contextlib.suppress(ValidationError):
124
+ response_msg = r_type.parse_raw(response_json)
125
+
126
+ return response_msg or response_json
127
+
128
+
84
129
  def send_message_to_agent(
85
130
  destination: str,
86
131
  msg: Model,
@@ -90,7 +135,9 @@ def send_message_to_agent(
90
135
  strategy: Literal["first", "random", "all"] = "first",
91
136
  agentverse_config: AgentverseConfig | None = None,
92
137
  resolver: Resolver | None = None,
93
- ) -> list[MsgStatus]:
138
+ sync: bool = False,
139
+ response_type: type[Model] | set[type[Model]] | None = None,
140
+ ) -> list[MsgStatus] | Model | JsonStr:
94
141
  """
95
142
  Send a message to an agent with default settings.
96
143
 
@@ -102,11 +149,21 @@ def send_message_to_agent(
102
149
  strategy (Literal["first", "random", "all"], optional): The strategy to use when
103
150
  selecting an endpoint.
104
151
  agentverse_config (AgentverseConfig, optional): The configuration for the agentverse.
152
+ resolver (Resolver, optional): The resolver to use for finding endpoints.
153
+ sync (bool, optional): Whether to send the message synchronously and wait for a response.
154
+ response_type (type[Model] | set[type[Model]] | None, optional):
155
+ The expected response type(s) for a sync message.
156
+
157
+ Returns:
158
+ list[MsgStatus] | Model | JsonStr: A list of message statuses
159
+ or the response model or json string if sync is True.
105
160
  """
106
161
  agentverse_config = agentverse_config or AgentverseConfig()
107
162
 
108
163
  if not resolver:
164
+ max_endpoints = 1 if strategy in ["first", "random"] else DEFAULT_MAX_ENDPOINTS
109
165
  resolver = AlmanacResolver(
166
+ max_endpoints=max_endpoints,
110
167
  agentverse_config=agentverse_config,
111
168
  )
112
169
  endpoints = resolver.sync_resolve(destination)
@@ -121,31 +178,24 @@ def send_message_to_agent(
121
178
  sender=sender,
122
179
  session_id=session_id,
123
180
  )
124
- match strategy:
125
- case "first":
126
- endpoints = endpoints[:1]
127
- case "random":
128
- endpoints = weighted_random_sample(endpoints)
129
-
130
- endpoints: list[str] = endpoints if strategy == "all" else endpoints[:1]
131
181
 
132
- result: list[MsgStatus] = []
182
+ status_result: list[MsgStatus] = []
133
183
  for endpoint in endpoints:
134
184
  try:
135
- response = send_message(endpoint, env)
136
- logger.info("Sent message to agent", extra={"agent_endpoint": endpoint})
137
- result.append(
185
+ response = send_message(endpoint, env, sync=True)
186
+ status_result.append(
138
187
  MsgStatus(
139
188
  status=DeliveryStatus.SENT,
140
- detail=response.text,
189
+ detail="Message sent successfully",
141
190
  destination=destination,
142
191
  endpoint=endpoint,
143
- session=env.session,
192
+ session=session_id,
144
193
  )
145
194
  )
195
+ break
146
196
  except requests.RequestException as e:
147
197
  logger.error("Failed to send message to agent", extra={"error": str(e)})
148
- result.append(
198
+ status_result.append(
149
199
  MsgStatus(
150
200
  status=DeliveryStatus.FAILED,
151
201
  detail=str(e),
@@ -154,4 +204,16 @@ def send_message_to_agent(
154
204
  session=env.session,
155
205
  )
156
206
  )
157
- return result
207
+
208
+ logger.info("Sent message to agent", extra={"agent_endpoint": endpoint})
209
+
210
+ if sync:
211
+ try:
212
+ return parse_sync_response(response.text, response_type)
213
+ except ValidationError as e:
214
+ logger.error(
215
+ "Received invalid response envelope",
216
+ extra={"error": str(e), "response": response.text},
217
+ )
218
+
219
+ return status_result
@@ -75,17 +75,24 @@ def lookup_endpoint_for_agent(
75
75
 
76
76
 
77
77
  class AlmanacResolver(Resolver):
78
- def __init__(self, agentverse_config: AgentverseConfig | None = None):
78
+ def __init__(
79
+ self, max_endpoints: int = 1, agentverse_config: AgentverseConfig | None = None
80
+ ):
79
81
  self.agentverse_config = agentverse_config or AgentverseConfig()
82
+ self.max_endpoints = max_endpoints
80
83
 
81
84
  async def resolve(self, destination: str) -> tuple[str | None, list[str]]:
82
85
  endpoints = lookup_endpoint_for_agent(
83
- agent_identifier=destination, agentverse_config=self.agentverse_config
86
+ agent_identifier=destination,
87
+ max_endpoints=self.max_endpoints,
88
+ agentverse_config=self.agentverse_config,
84
89
  )
85
90
  return None, endpoints
86
91
 
87
92
  def sync_resolve(self, destination: str) -> list[str]:
88
93
  endpoints = lookup_endpoint_for_agent(
89
- agent_identifier=destination, agentverse_config=self.agentverse_config
94
+ agent_identifier=destination,
95
+ max_endpoints=self.max_endpoints,
96
+ agentverse_config=self.agentverse_config,
90
97
  )
91
98
  return endpoints
File without changes