agentstr 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
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.
- agentstr/__init__.py +36 -17
- agentstr/buyer.py +305 -0
- agentstr/buyer.pyi +32 -0
- agentstr/{marketplace.py → merchant.py} +167 -355
- agentstr/merchant.pyi +37 -0
- agentstr/models.py +380 -0
- agentstr/models.pyi +103 -0
- agentstr/nostr.py +412 -79
- agentstr/nostr.pyi +96 -0
- agentstr/py.typed +0 -0
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/METADATA +42 -56
- agentstr-0.1.13.dist-info/RECORD +15 -0
- agentstr-0.1.11.dist-info/RECORD +0 -8
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/LICENSE +0 -0
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/WHEEL +0 -0
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/top_level.txt +0 -0
agentstr/__init__.py
CHANGED
@@ -1,30 +1,49 @@
|
|
1
1
|
"""
|
2
|
-
AgentStr: Nostr extension for
|
2
|
+
AgentStr: Nostr extension for Agno AI agents
|
3
3
|
"""
|
4
4
|
|
5
|
+
import importlib.metadata
|
6
|
+
import logging
|
7
|
+
|
8
|
+
from nostr_sdk import ShippingCost, ShippingMethod, Timestamp # type: ignore
|
9
|
+
|
10
|
+
from agentstr.nostr import EventId, Keys, Kind, NostrClient, generate_and_save_keys
|
11
|
+
|
12
|
+
from .buyer import BuyerTools
|
13
|
+
from .merchant import MerchantTools
|
14
|
+
|
5
15
|
# Import main classes to make them available at package level
|
6
|
-
from .
|
7
|
-
Merchant,
|
8
|
-
MerchantProduct,
|
9
|
-
MerchantStall,
|
10
|
-
Profile,
|
11
|
-
ShippingCost,
|
12
|
-
ShippingMethod,
|
13
|
-
)
|
16
|
+
from .models import AgentProfile, MerchantProduct, MerchantStall, NostrProfile
|
14
17
|
|
15
18
|
# Import version from pyproject.toml at runtime
|
16
19
|
try:
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
__version__ = importlib.metadata.version("agentstr")
|
21
|
+
except importlib.metadata.PackageNotFoundError:
|
22
|
+
logging.warning("Package 'agentstr' not found. Falling back to 'unknown'.")
|
23
|
+
__version__ = "unknown"
|
24
|
+
except ImportError:
|
25
|
+
logging.warning("importlib.metadata is not available. Falling back to 'unknown'.")
|
21
26
|
__version__ = "unknown"
|
22
27
|
|
28
|
+
# Define What is Exposed at the Package Level
|
23
29
|
__all__ = [
|
24
|
-
|
25
|
-
"
|
26
|
-
"MerchantStall",
|
30
|
+
# Merchant Tools
|
31
|
+
"MerchantTools",
|
27
32
|
"MerchantProduct",
|
28
|
-
"
|
33
|
+
"MerchantStall",
|
34
|
+
# Buyer Tools
|
35
|
+
"BuyerTools",
|
36
|
+
# Shipping
|
29
37
|
"ShippingCost",
|
38
|
+
"ShippingMethod",
|
39
|
+
# Nostr-related utils
|
40
|
+
"EventId",
|
41
|
+
"Keys",
|
42
|
+
"Kind",
|
43
|
+
"NostrClient",
|
44
|
+
"generate_and_save_keys",
|
45
|
+
"Timestamp",
|
46
|
+
# Models
|
47
|
+
"AgentProfile",
|
48
|
+
"NostrProfile",
|
30
49
|
]
|
agentstr/buyer.py
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
"""
|
2
|
+
Module implementing the BuyerTools Toolkit for Agno agents.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
from uuid import uuid4
|
8
|
+
|
9
|
+
from pydantic import ConfigDict
|
10
|
+
|
11
|
+
from agentstr.models import AgentProfile, NostrProfile
|
12
|
+
from agentstr.nostr import NostrClient, PublicKey
|
13
|
+
|
14
|
+
try:
|
15
|
+
from agno.agent import AgentKnowledge # type: ignore
|
16
|
+
from agno.document.base import Document
|
17
|
+
from agno.tools import Toolkit
|
18
|
+
except ImportError as exc:
|
19
|
+
raise ImportError(
|
20
|
+
"`agno` not installed. Please install using `pip install agno`"
|
21
|
+
) from exc
|
22
|
+
|
23
|
+
|
24
|
+
def _map_location_to_geohash(location: str) -> str:
|
25
|
+
"""
|
26
|
+
Map a location to a geohash.
|
27
|
+
|
28
|
+
TBD: Implement this function. Returning a fixed geohash for now.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
location: location to map to a geohash. Can be a zip code, city,
|
32
|
+
state, country, or latitude and longitude.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
str: geohash of the location or empty string if location is not found
|
36
|
+
"""
|
37
|
+
if "snoqualmie" in location.lower():
|
38
|
+
return "C23Q7U36W"
|
39
|
+
|
40
|
+
return ""
|
41
|
+
|
42
|
+
|
43
|
+
class BuyerTools(Toolkit):
|
44
|
+
"""
|
45
|
+
BuyerTools is a toolkit that allows an agent to find sellers and
|
46
|
+
transact with them over Nostr.
|
47
|
+
|
48
|
+
Sellers are downloaded from the Nostr relay and cached.
|
49
|
+
Sellers can be found by name or public key.
|
50
|
+
Sellers cache can be refreshed from the Nostr relay.
|
51
|
+
Sellers can be retrieved as a list of Nostr profiles.
|
52
|
+
|
53
|
+
TBD: populate the sellers locations with info from stalls.
|
54
|
+
"""
|
55
|
+
|
56
|
+
model_config = ConfigDict(
|
57
|
+
arbitrary_types_allowed=True, extra="allow", validate_assignment=True
|
58
|
+
)
|
59
|
+
|
60
|
+
logger = logging.getLogger("Buyer")
|
61
|
+
sellers: set[NostrProfile] = set()
|
62
|
+
|
63
|
+
def __init__(
|
64
|
+
self,
|
65
|
+
knowledge_base: AgentKnowledge,
|
66
|
+
buyer_profile: AgentProfile,
|
67
|
+
relay: str,
|
68
|
+
) -> None:
|
69
|
+
"""Initialize the Buyer toolkit.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
knowledge_base: knowledge base of the buyer agent
|
73
|
+
buyer_profile: profile of the buyer using this agent
|
74
|
+
relay: Nostr relay to use for communications
|
75
|
+
"""
|
76
|
+
super().__init__(name="Buyer")
|
77
|
+
|
78
|
+
self.relay = relay
|
79
|
+
self.buyer_profile = buyer_profile
|
80
|
+
self.knowledge_base = knowledge_base
|
81
|
+
# Initialize fields
|
82
|
+
self._nostr_client = NostrClient(relay, buyer_profile.get_private_key())
|
83
|
+
|
84
|
+
# Register methods
|
85
|
+
self.register(self.find_seller_by_name)
|
86
|
+
self.register(self.find_seller_by_public_key)
|
87
|
+
self.register(self.find_sellers_by_location)
|
88
|
+
self.register(self.get_profile)
|
89
|
+
self.register(self.get_relay)
|
90
|
+
self.register(self.get_seller_stalls)
|
91
|
+
self.register(self.get_seller_products)
|
92
|
+
self.register(self.get_seller_count)
|
93
|
+
self.register(self.get_sellers)
|
94
|
+
self.register(self.refresh_sellers)
|
95
|
+
self.register(self.purchase_product)
|
96
|
+
|
97
|
+
def purchase_product(self, product: str) -> str:
|
98
|
+
"""Purchase a product.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
product: JSON string with product to purchase
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
str: JSON string with status and message
|
105
|
+
"""
|
106
|
+
return json.dumps(
|
107
|
+
{"status": "success", "message": f"Product {product} purchased"}
|
108
|
+
)
|
109
|
+
|
110
|
+
def find_seller_by_name(self, name: str) -> str:
|
111
|
+
"""Find a seller by name.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
name: name of the seller to find
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
str: JSON string with seller profile or error message
|
118
|
+
"""
|
119
|
+
for seller in self.sellers:
|
120
|
+
if seller.get_name() == name:
|
121
|
+
response = seller.to_json()
|
122
|
+
# self._store_response_in_knowledge_base(response)
|
123
|
+
return response
|
124
|
+
response = json.dumps({"status": "error", "message": "Seller not found"})
|
125
|
+
self._store_response_in_knowledge_base(response)
|
126
|
+
return response
|
127
|
+
|
128
|
+
def find_seller_by_public_key(self, public_key: str) -> str:
|
129
|
+
"""Find a seller by public key.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
public_key: bech32 encoded public key of the seller to find
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
str: seller profile json string or error message
|
136
|
+
"""
|
137
|
+
for seller in self.sellers:
|
138
|
+
if seller.get_public_key() == public_key:
|
139
|
+
response = seller.to_json()
|
140
|
+
# self._store_response_in_knowledge_base(response)
|
141
|
+
return response
|
142
|
+
response = json.dumps({"status": "error", "message": "Seller not found"})
|
143
|
+
self._store_response_in_knowledge_base(response)
|
144
|
+
return response
|
145
|
+
|
146
|
+
def find_sellers_by_location(self, location: str) -> str:
|
147
|
+
"""Find sellers by location.
|
148
|
+
|
149
|
+
Args:
|
150
|
+
location: location of the seller to find (e.g. "San Francisco, CA")
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
str: list of seller profile json strings or error message
|
154
|
+
"""
|
155
|
+
sellers: set[NostrProfile] = set()
|
156
|
+
geohash = _map_location_to_geohash(location)
|
157
|
+
# print(f"find_sellers_by_location: geohash: {geohash}")
|
158
|
+
|
159
|
+
if not geohash:
|
160
|
+
response = json.dumps({"status": "error", "message": "Invalid location"})
|
161
|
+
return response
|
162
|
+
|
163
|
+
# Find sellers in the same geohash
|
164
|
+
for seller in self.sellers:
|
165
|
+
if geohash in seller.get_locations():
|
166
|
+
sellers.add(seller)
|
167
|
+
|
168
|
+
if not sellers:
|
169
|
+
response = json.dumps(
|
170
|
+
{"status": "error", "message": f"No sellers found near {location}"}
|
171
|
+
)
|
172
|
+
return response
|
173
|
+
|
174
|
+
response = json.dumps([seller.to_dict() for seller in sellers])
|
175
|
+
# print("find_sellers_by_location: storing response in knowledge base")
|
176
|
+
self._store_response_in_knowledge_base(response)
|
177
|
+
self.logger.info("Found %d sellers", len(sellers))
|
178
|
+
return response
|
179
|
+
|
180
|
+
def get_nostr_client(self) -> NostrClient:
|
181
|
+
"""Get the Nostr client.
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
NostrClient: Nostr client
|
185
|
+
"""
|
186
|
+
return self._nostr_client
|
187
|
+
|
188
|
+
def get_profile(self) -> str:
|
189
|
+
"""Get the Nostr profile of the buyer agent.
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
str: buyer profile json string
|
193
|
+
"""
|
194
|
+
response = self.buyer_profile.to_json()
|
195
|
+
self._store_response_in_knowledge_base(response)
|
196
|
+
return response
|
197
|
+
|
198
|
+
def get_relay(self) -> str:
|
199
|
+
"""Get the Nostr relay that the buyer agent is using.
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
str: Nostr relay
|
203
|
+
"""
|
204
|
+
response = self.relay
|
205
|
+
# self._store_response_in_knowledge_base(response)
|
206
|
+
return response
|
207
|
+
|
208
|
+
def get_seller_stalls(self, public_key: str) -> str:
|
209
|
+
"""Get the stalls from a seller.
|
210
|
+
|
211
|
+
Args:
|
212
|
+
public_key: public key of the seller
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
str: JSON string with seller collections
|
216
|
+
"""
|
217
|
+
try:
|
218
|
+
stalls = self._nostr_client.retrieve_stalls_from_seller(
|
219
|
+
PublicKey.parse(public_key)
|
220
|
+
)
|
221
|
+
response = json.dumps([stall.as_json() for stall in stalls])
|
222
|
+
self._store_response_in_knowledge_base(response)
|
223
|
+
return response
|
224
|
+
except RuntimeError as e:
|
225
|
+
response = json.dumps({"status": "error", "message": str(e)})
|
226
|
+
return response
|
227
|
+
|
228
|
+
def get_seller_count(self) -> str:
|
229
|
+
"""Get the number of sellers.
|
230
|
+
|
231
|
+
Returns:
|
232
|
+
str: JSON string with status and count of sellers
|
233
|
+
"""
|
234
|
+
response = json.dumps({"status": "success", "count": len(self.sellers)})
|
235
|
+
return response
|
236
|
+
|
237
|
+
def get_seller_products(self, public_key: str) -> str:
|
238
|
+
"""Get the products from a seller
|
239
|
+
|
240
|
+
Args:
|
241
|
+
public_key: public key of the seller
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
str: JSON string with seller products
|
245
|
+
"""
|
246
|
+
try:
|
247
|
+
products = self._nostr_client.retrieve_products_from_seller(
|
248
|
+
PublicKey.parse(public_key)
|
249
|
+
)
|
250
|
+
|
251
|
+
response = json.dumps([product.to_dict() for product in products])
|
252
|
+
self._store_response_in_knowledge_base(response)
|
253
|
+
return response
|
254
|
+
except RuntimeError as e:
|
255
|
+
response = json.dumps({"status": "error", "message": str(e)})
|
256
|
+
return response
|
257
|
+
|
258
|
+
def get_sellers(self) -> str:
|
259
|
+
"""Get the list of sellers.
|
260
|
+
If no sellers are cached, the list is refreshed from the Nostr relay.
|
261
|
+
If sellers are cached, the list is returned from the cache.
|
262
|
+
To get a fresh list of sellers, call refresh_sellers() sellers first.
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
str: list of sellers json strings
|
266
|
+
"""
|
267
|
+
if not self.sellers:
|
268
|
+
self._refresh_sellers()
|
269
|
+
response = json.dumps([seller.to_json() for seller in self.sellers])
|
270
|
+
return response
|
271
|
+
|
272
|
+
def refresh_sellers(self) -> str:
|
273
|
+
"""Refresh the list of sellers.
|
274
|
+
|
275
|
+
Returns:
|
276
|
+
str: JSON string with status and count of sellers refreshed
|
277
|
+
"""
|
278
|
+
self._refresh_sellers()
|
279
|
+
response = json.dumps({"status": "success", "count": len(self.sellers)})
|
280
|
+
return response
|
281
|
+
|
282
|
+
def _refresh_sellers(self) -> None:
|
283
|
+
"""
|
284
|
+
Internal fucntion to retrieve a new list of sellers from the Nostr relay.
|
285
|
+
The old list is discarded and the new list only contains unique sellers
|
286
|
+
currently stored at the relay.
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
List[NostrProfile]: List of Nostr profiles of all sellers.
|
290
|
+
"""
|
291
|
+
sellers = self._nostr_client.retrieve_sellers()
|
292
|
+
if len(sellers) == 0:
|
293
|
+
self.logger.info("No sellers found")
|
294
|
+
else:
|
295
|
+
self.logger.info("Found %d sellers", len(sellers))
|
296
|
+
|
297
|
+
self.sellers = sellers
|
298
|
+
|
299
|
+
def _store_response_in_knowledge_base(self, response: str) -> None:
|
300
|
+
doc = Document(
|
301
|
+
id=str(uuid4()),
|
302
|
+
content=response,
|
303
|
+
)
|
304
|
+
# print(f"Document length: {len(doc.content.split())} words")
|
305
|
+
self.knowledge_base.load_documents([doc]) # Store response in Cassandra
|
agentstr/buyer.pyi
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
from logging import Logger
|
2
|
+
from typing import ClassVar
|
3
|
+
|
4
|
+
from agno.agent import AgentKnowledge
|
5
|
+
from agno.tools import Toolkit
|
6
|
+
|
7
|
+
from agentstr.models import AgentProfile, NostrProfile
|
8
|
+
from agentstr.nostr import NostrClient
|
9
|
+
|
10
|
+
class BuyerTools(Toolkit):
|
11
|
+
logger: ClassVar[Logger]
|
12
|
+
sellers: set[NostrProfile]
|
13
|
+
relay: str
|
14
|
+
_nostr_client: NostrClient
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self, knowledge_base: AgentKnowledge, buyer_profile: AgentProfile, relay: str
|
18
|
+
) -> None: ...
|
19
|
+
def find_seller_by_name(self, name: str) -> str: ...
|
20
|
+
def find_seller_by_public_key(self, public_key: str) -> str: ...
|
21
|
+
def find_sellers_by_location(self, location: str) -> str: ...
|
22
|
+
def get_nostr_client(self) -> NostrClient: ...
|
23
|
+
def get_profile(self) -> str: ...
|
24
|
+
def get_relay(self) -> str: ...
|
25
|
+
def get_seller_stalls(self, public_key: str) -> str: ...
|
26
|
+
def get_seller_count(self) -> str: ...
|
27
|
+
def get_seller_products(self, public_key: str) -> str: ...
|
28
|
+
def get_sellers(self) -> str: ...
|
29
|
+
def purchase_product(self, product: str) -> str: ...
|
30
|
+
def refresh_sellers(self) -> str: ...
|
31
|
+
def _refresh_sellers(self) -> None: ...
|
32
|
+
def _store_response_in_knowledge_base(self, response: str) -> None: ...
|