agentstr 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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: ...
|